summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/file_path.cc85
-rw-r--r--base/file_path.h33
-rw-r--r--base/file_path_unittest.cc159
-rw-r--r--base/file_util.h22
4 files changed, 282 insertions, 17 deletions
diff --git a/base/file_path.cc b/base/file_path.cc
index 9a645b0..8738c37 100644
--- a/base/file_path.cc
+++ b/base/file_path.cc
@@ -21,6 +21,9 @@ const FilePath::CharType FilePath::kSeparators[] = FILE_PATH_LITERAL("/");
const FilePath::CharType FilePath::kCurrentDirectory[] = FILE_PATH_LITERAL(".");
const FilePath::CharType FilePath::kParentDirectory[] = FILE_PATH_LITERAL("..");
+const FilePath::CharType FilePath::kExtensionSeparator = FILE_PATH_LITERAL('.');
+
+
namespace {
// If this FilePath contains a drive letter specification, returns the
@@ -38,10 +41,8 @@ FilePath::StringType::size_type FindDriveLetter(
(path[0] >= L'a' && path[0] <= L'z'))) {
return 1;
}
- return FilePath::StringType::npos;
-#else // FILE_PATH_USES_DRIVE_LETTERS
- return FilePath::StringType::npos;
#endif // FILE_PATH_USES_DRIVE_LETTERS
+ return FilePath::StringType::npos;
}
bool IsPathAbsolute(const FilePath::StringType& path) {
@@ -136,7 +137,83 @@ FilePath FilePath::BaseName() const {
return new_path;
}
-FilePath FilePath::Append(const FilePath::StringType& component) const {
+FilePath::StringType FilePath::Extension() const {
+ // BaseName() calls StripTrailingSeparators, so cases like /foo.baz/// work.
+ StringType base = BaseName().value();
+
+ // Special case "." and ".."
+ if (base == kCurrentDirectory || base == kParentDirectory)
+ return StringType();
+
+ const StringType::size_type last_dot = base.rfind(kExtensionSeparator);
+ if (last_dot == StringType::npos)
+ return StringType();
+ return StringType(base, last_dot);
+}
+
+FilePath FilePath::RemoveExtension() const {
+ StringType ext = Extension();
+ // It's important to check Extension() since that verifies that the
+ // kExtensionSeparator actually appeared in the last path component.
+ if (ext.empty())
+ return FilePath(path_);
+ // Since Extension() verified that the extension is in fact in the last path
+ // component, this substr will effectively strip trailing separators.
+ const StringType::size_type last_dot = path_.rfind(kExtensionSeparator);
+ return FilePath(path_.substr(0, last_dot));
+}
+
+FilePath FilePath::InsertBeforeExtension(const StringType& suffix) const {
+ if (suffix.empty())
+ return FilePath(path_);
+
+ if (path_.empty())
+ return FilePath();
+
+ StringType base = BaseName().value();
+ if (base.empty())
+ return FilePath();
+ if (*(base.end() - 1) == kExtensionSeparator) {
+ // Special case "." and ".."
+ if (base == kCurrentDirectory || base == kParentDirectory) {
+ return FilePath();
+ }
+ }
+
+ StringType ext = Extension();
+ StringType ret = RemoveExtension().value();
+ ret.append(suffix);
+ ret.append(ext);
+ return FilePath(ret);
+}
+
+FilePath FilePath::ReplaceExtension(const StringType& extension) const {
+ if (path_.empty())
+ return FilePath();
+
+ StringType base = BaseName().value();
+ if (base.empty())
+ return FilePath();
+ if (*(base.end() - 1) == kExtensionSeparator) {
+ // Special case "." and ".."
+ if (base == kCurrentDirectory || base == kParentDirectory) {
+ return FilePath();
+ }
+ }
+
+ FilePath no_ext = RemoveExtension();
+ // If the new extension is "" or ".", then just remove the current extension.
+ if (extension.empty() || extension == StringType(1, kExtensionSeparator))
+ return no_ext;
+
+ StringType str = no_ext.value();
+ if (extension[0] != kExtensionSeparator)
+ str.append(1, kExtensionSeparator);
+ str.append(extension);
+ return FilePath(str);
+}
+
+FilePath FilePath::Append(const StringType& component) const {
DCHECK(!IsPathAbsolute(component));
if (path_.compare(kCurrentDirectory) == 0) {
// Append normally doesn't do any normalization, but as a special case,
diff --git a/base/file_path.h b/base/file_path.h
index 6ed9320..d80298d 100644
--- a/base/file_path.h
+++ b/base/file_path.h
@@ -109,6 +109,9 @@ class FilePath {
// A special path component meaning "the parent directory."
static const CharType kParentDirectory[];
+ // The character used to identify a file extension.
+ static const CharType kExtensionSeparator;
+
FilePath() {}
FilePath(const FilePath& that) : path_(that.path_) {}
explicit FilePath(const StringType& path) : path_(path) {}
@@ -147,6 +150,36 @@ class FilePath {
// this is the only situation in which BaseName will return an absolute path.
FilePath BaseName() const;
+ // Returns ".jpg" for path "C:\pics\jojo.jpg", or an empty string if
+ // the file has no extension. If non-empty, Extension() will always start
+ // with precisely one ".". The following code should always work regardless
+ // of the value of path.
+ // new_path = path.RemoveExtension().value().append(path.Extension());
+ // ASSERT(new_path == path.value());
+ // NOTE: this is different from the original file_util implementation which
+ // returned the extension without a leading "." ("jpg" instead of ".jpg")
+ StringType Extension() const;
+
+ // Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg"
+ // NOTE: this is slightly different from the similar file_util implementation
+ // which returned simply 'jojo'.
+ FilePath RemoveExtension() const;
+
+ // Inserts |suffix| after the file name portion of |path| but before the
+ // extension. Returns "" if BaseName() == "." or "..".
+ // Examples:
+ // path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg"
+ // path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg"
+ // path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)"
+ // path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)"
+ FilePath InsertBeforeExtension(const StringType& suffix) const;
+
+ // Replaces the extension of |file_name| with |extension|. If |file_name|
+ // does not have an extension, them |extension| is added. If |extension| is
+ // empty, then the extension is removed from |file_name|.
+ // Returns "" if BaseName() == "." or "..".
+ FilePath ReplaceExtension(const StringType& extension) const;
+
// Returns a FilePath by appending a separator and the supplied path
// component to this object's path. Append takes care to avoid adding
// excessive separators if this object's path already ends with a separator.
diff --git a/base/file_path_unittest.cc b/base/file_path_unittest.cc
index b4ad16f..ac45e46 100644
--- a/base/file_path_unittest.cc
+++ b/base/file_path_unittest.cc
@@ -406,3 +406,162 @@ TEST_F(FilePathTest, Contains) {
// Note: whether
}
+
+TEST_F(FilePathTest, Extension) {
+ FilePath base_dir(FILE_PATH_LITERAL("base_dir"));
+
+ FilePath jpg = base_dir.Append(FILE_PATH_LITERAL("foo.jpg"));
+ EXPECT_EQ(jpg.Extension(), FILE_PATH_LITERAL(".jpg"));
+
+ FilePath base = jpg.BaseName().RemoveExtension();
+ EXPECT_EQ(base.value(), FILE_PATH_LITERAL("foo"));
+
+ FilePath path_no_ext = base_dir.Append(base);
+ EXPECT_EQ(jpg.RemoveExtension().value(), path_no_ext.value());
+
+ EXPECT_EQ(path_no_ext.value(), path_no_ext.RemoveExtension().value());
+ EXPECT_EQ(path_no_ext.Extension(), FILE_PATH_LITERAL(""));
+}
+
+TEST_F(FilePathTest, Extension2) {
+ const struct UnaryTestData cases[] = {
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { FPL("C:\\a\\b\\c.ext"), FPL(".ext") },
+ { FPL("C:\\a\\b\\c."), FPL(".") },
+ { FPL("C:\\a\\b\\c"), FPL("") },
+ { FPL("C:\\a\\b\\"), FPL("") },
+ { FPL("C:\\a\\b.\\"), FPL(".") },
+ { FPL("C:\\a\\b\\c.ext1.ext2"), FPL(".ext2") },
+ { FPL("C:\\foo.bar\\\\\\"), FPL(".bar") },
+ { FPL("C:\\foo.bar\\.."), FPL("") },
+ { FPL("C:\\foo.bar\\..\\\\"), FPL("") },
+#endif
+ { FPL("/foo/bar/baz.ext"), FPL(".ext") },
+ { FPL("/foo/bar/baz."), FPL(".") },
+ { FPL("/foo/bar/baz.."), FPL(".") },
+ { FPL("/foo/bar/baz"), FPL("") },
+ { FPL("/foo/bar/"), FPL("") },
+ { FPL("/foo/bar./"), FPL(".") },
+ { FPL("/foo/bar/baz.ext1.ext2"), FPL(".ext2") },
+ { FPL("."), FPL("") },
+ { FPL(".."), FPL("") },
+ { FPL("./foo"), FPL("") },
+ { FPL("./foo.ext"), FPL(".ext") },
+ { FPL("/foo.ext1/bar.ext2"), FPL(".ext2") },
+ { FPL("/foo.bar////"), FPL(".bar") },
+ { FPL("/foo.bar/.."), FPL("") },
+ { FPL("/foo.bar/..////"), FPL("") },
+ };
+ for (unsigned int i = 0; i < arraysize(cases); ++i) {
+ FilePath path(cases[i].input);
+ FilePath::StringType extension = path.Extension();
+ EXPECT_STREQ(cases[i].expected, extension.c_str()) << "i: " << i <<
+ ", path: " << path.value();
+ }
+}
+
+TEST_F(FilePathTest, InsertBeforeExtension) {
+ const struct BinaryTestData cases[] = {
+ { { FPL(""), FPL("") }, FPL("") },
+ { { FPL(""), FPL("txt") }, FPL("") },
+ { { FPL("."), FPL("txt") }, FPL("") },
+ { { FPL(".."), FPL("txt") }, FPL("") },
+ { { FPL("foo.dll"), FPL("txt") }, FPL("footxt.dll") },
+ { { FPL("."), FPL("") }, FPL(".") },
+ { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt.dll") },
+ { { FPL("foo"), FPL("txt") }, FPL("footxt") },
+ { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") },
+ { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baztxt.dll") },
+ { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt.dll") },
+ { { FPL("foo.dll"), FPL("") }, FPL("foo.dll") },
+ { { FPL("foo.dll"), FPL(".") }, FPL("foo..dll") },
+ { { FPL("foo"), FPL("") }, FPL("foo") },
+ { { FPL("foo"), FPL(".") }, FPL("foo.") },
+ { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz.dll") },
+ { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz..dll") },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { { FPL("\\"), FPL("") }, FPL("\\") },
+ { { FPL("\\"), FPL("txt") }, FPL("\\txt") },
+ { { FPL("\\."), FPL("txt") }, FPL("") },
+ { { FPL("\\.."), FPL("txt") }, FPL("") },
+ { { FPL("\\."), FPL("") }, FPL("\\.") },
+ { { FPL("C:\\bar\\foo.dll"), FPL("txt") },
+ FPL("C:\\bar\\footxt.dll") },
+ { { FPL("C:\\bar.baz\\foodll"), FPL("txt") },
+ FPL("C:\\bar.baz\\foodlltxt") },
+ { { FPL("C:\\bar.baz\\foo.dll"), FPL("txt") },
+ FPL("C:\\bar.baz\\footxt.dll") },
+ { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("txt") },
+ FPL("C:\\bar.baz\\foo.dlltxt.exe") },
+ { { FPL("C:\\bar.baz\\foo"), FPL("") },
+ FPL("C:\\bar.baz\\foo") },
+ { { FPL("C:\\bar.baz\\foo.exe"), FPL("") },
+ FPL("C:\\bar.baz\\foo.exe") },
+ { { FPL("C:\\bar.baz\\foo.dll.exe"), FPL("") },
+ FPL("C:\\bar.baz\\foo.dll.exe") },
+ { { FPL("C:\\bar\\baz\\foo.exe"), FPL(" (1)") },
+ FPL("C:\\bar\\baz\\foo (1).exe") },
+ { { FPL("C:\\foo.baz\\\\"), FPL(" (1)") }, FPL("C:\\foo (1).baz") },
+ { { FPL("C:\\foo.baz\\..\\"), FPL(" (1)") }, FPL("") },
+#endif
+ { { FPL("/"), FPL("") }, FPL("/") },
+ { { FPL("/"), FPL("txt") }, FPL("/txt") },
+ { { FPL("/."), FPL("txt") }, FPL("") },
+ { { FPL("/.."), FPL("txt") }, FPL("") },
+ { { FPL("/."), FPL("") }, FPL("/.") },
+ { { FPL("/bar/foo.dll"), FPL("txt") }, FPL("/bar/footxt.dll") },
+ { { FPL("/bar.baz/foodll"), FPL("txt") }, FPL("/bar.baz/foodlltxt") },
+ { { FPL("/bar.baz/foo.dll"), FPL("txt") }, FPL("/bar.baz/footxt.dll") },
+ { { FPL("/bar.baz/foo.dll.exe"), FPL("txt") },
+ FPL("/bar.baz/foo.dlltxt.exe") },
+ { { FPL("/bar.baz/foo"), FPL("") }, FPL("/bar.baz/foo") },
+ { { FPL("/bar.baz/foo.exe"), FPL("") }, FPL("/bar.baz/foo.exe") },
+ { { FPL("/bar.baz/foo.dll.exe"), FPL("") }, FPL("/bar.baz/foo.dll.exe") },
+ { { FPL("/bar/baz/foo.exe"), FPL(" (1)") }, FPL("/bar/baz/foo (1).exe") },
+ { { FPL("/bar/baz/..////"), FPL(" (1)") }, FPL("") },
+ };
+ for (unsigned int i = 0; i < arraysize(cases); ++i) {
+ FilePath path(cases[i].inputs[0]);
+ FilePath result = path.InsertBeforeExtension(cases[i].inputs[1]);
+ EXPECT_EQ(cases[i].expected, result.value()) << "i: " << i <<
+ ", path: " << path.value() << ", insert: " << cases[i].inputs[1];
+ }
+}
+
+TEST_F(FilePathTest, ReplaceExtension) {
+ const struct BinaryTestData cases[] = {
+ { { FPL(""), FPL("") }, FPL("") },
+ { { FPL(""), FPL("txt") }, FPL("") },
+ { { FPL("."), FPL("txt") }, FPL("") },
+ { { FPL(".."), FPL("txt") }, FPL("") },
+ { { FPL("."), FPL("") }, FPL("") },
+ { { FPL("foo.dll"), FPL("txt") }, FPL("foo.txt") },
+ { { FPL("foo..dll"), FPL("txt") }, FPL("foo..txt") },
+ { { FPL("foo.dll"), FPL(".txt") }, FPL("foo.txt") },
+ { { FPL("foo"), FPL("txt") }, FPL("foo.txt") },
+ { { FPL("foo."), FPL("txt") }, FPL("foo.txt") },
+ { { FPL("foo.."), FPL("txt") }, FPL("foo..txt") },
+ { { FPL("foo"), FPL(".txt") }, FPL("foo.txt") },
+ { { FPL("foo.baz.dll"), FPL("txt") }, FPL("foo.baz.txt") },
+ { { FPL("foo.baz.dll"), FPL(".txt") }, FPL("foo.baz.txt") },
+ { { FPL("foo.dll"), FPL("") }, FPL("foo") },
+ { { FPL("foo.dll"), FPL(".") }, FPL("foo") },
+ { { FPL("foo"), FPL("") }, FPL("foo") },
+ { { FPL("foo"), FPL(".") }, FPL("foo") },
+ { { FPL("foo.baz.dll"), FPL("") }, FPL("foo.baz") },
+ { { FPL("foo.baz.dll"), FPL(".") }, FPL("foo.baz") },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { { FPL("C:\\foo.bar\\foo"), FPL("baz") }, FPL("C:\\foo.bar\\foo.baz") },
+ { { FPL("C:\\foo.bar\\..\\\\"), FPL("baz") }, FPL("") },
+#endif
+ { { FPL("/foo.bar/foo"), FPL("baz") }, FPL("/foo.bar/foo.baz") },
+ { { FPL("/foo.bar/..////"), FPL("baz") }, FPL("") },
+ };
+ for (unsigned int i = 0; i < arraysize(cases); ++i) {
+ FilePath path(cases[i].inputs[0]);
+ FilePath replaced = path.ReplaceExtension(cases[i].inputs[1]);
+ EXPECT_EQ(cases[i].expected, replaced.value()) << "i: " << i <<
+ ", path: " << path.value() << ", replace: " << cases[i].inputs[1];
+ }
+}
+
diff --git a/base/file_util.h b/base/file_util.h
index 784ea36..c064b2c 100644
--- a/base/file_util.h
+++ b/base/file_util.h
@@ -75,13 +75,12 @@ void TrimFilename(std::wstring* path);
// Deprecated. Use FilePath::BaseName instead.
std::wstring GetFilenameFromPath(const std::wstring& path);
-// Returns "jpg" for path "C:\pics\jojo.jpg", or an empty string if
-// the file has no extension.
+// Deprecated compatibility function. Use FilePath::Extension.
FilePath::StringType GetFileExtensionFromPath(const FilePath& path);
// Deprecated temporary compatibility function.
std::wstring GetFileExtensionFromPath(const std::wstring& path);
-// Returns 'jojo' for path "C:\pics\jojo.jpg".
+// Deprecated compatibility function. Use FilePath::RemoveExtension.
std::wstring GetFilenameWithoutExtensionFromPath(const std::wstring& path);
// Returns the directory component of a path, without the trailing
@@ -104,18 +103,15 @@ bool AbsolutePath(FilePath* path);
// Deprecated temporary compatibility function.
bool AbsolutePath(std::wstring* path);
-// Inserts |suffix| after the file name portion of |path| but before the
-// extension.
-// Examples:
-// path == "C:\pics\jojo.jpg" suffix == " (1)", returns "C:\pics\jojo (1).jpg"
-// path == "jojo.jpg" suffix == " (1)", returns "jojo (1).jpg"
-// path == "C:\pics\jojo" suffix == " (1)", returns "C:\pics\jojo (1)"
-// path == "C:\pics.old\jojo" suffix == " (1)", returns "C:\pics.old\jojo (1)"
+// Returns true if this FilePath represents a parent dir of |other|. Both
+// paths are normalized before doing the comparison, but neither |this| nor
+// |other| are modified.
+bool PathContains(const FilePath& path, const FilePath& other);
+
+// Deprecated compatibility function. Use FilePath::InsertBeforeExtension.
void InsertBeforeExtension(FilePath* path, const FilePath::StringType& suffix);
-// Replaces the extension of |file_name| with |extension|. If |file_name|
-// does not have an extension, them |extension| is added. If |extension| is
-// empty, then the extension is removed from |file_name|.
+// Deprecated compatibility function. Use FilePath::ReplaceExtension.
void ReplaceExtension(FilePath* file_name,
const FilePath::StringType& extension);