From 332f96e7cab3bfc3814e6e08e0691c67d43b20f0 Mon Sep 17 00:00:00 2001 From: Brian Gianforcaro Date: Tue, 26 May 2020 02:12:18 -0700 Subject: [PATCH] AK: Add case insensitive String::ends_with support FileSystemPath::has_extension was jumping through hoops and allocating memory to do a case insensitive comparison needlessly. Extend the existing String::ends_with method to allow the caller to specify the case sensitivity required. --- AK/FileSystemPath.cpp | 6 ++---- AK/FileSystemPath.h | 2 +- AK/String.cpp | 4 ++-- AK/String.h | 2 +- AK/StringUtils.cpp | 16 ++++++++++++++-- AK/StringUtils.h | 3 +-- AK/Tests/TestFileSystemPath.cpp | 32 ++++++++++++++++++++++++++++++++ 7 files changed, 53 insertions(+), 12 deletions(-) diff --git a/AK/FileSystemPath.cpp b/AK/FileSystemPath.cpp index 21bb5fd3b3c..d79d31f5f60 100644 --- a/AK/FileSystemPath.cpp +++ b/AK/FileSystemPath.cpp @@ -105,11 +105,9 @@ void FileSystemPath::canonicalize() m_string = builder.to_string(); } -bool FileSystemPath::has_extension(StringView extension) const +bool FileSystemPath::has_extension(const StringView& extension) const { - // FIXME: This is inefficient, expand StringView with enough functionality that we don't need to copy strings here. - String extension_string = extension; - return m_string.to_lowercase().ends_with(extension_string.to_lowercase()); + return m_string.ends_with(extension, CaseSensitivity::CaseInsensitive); } String canonicalized_path(const StringView& path) diff --git a/AK/FileSystemPath.h b/AK/FileSystemPath.h index 2717501cf9c..5558945d995 100644 --- a/AK/FileSystemPath.h +++ b/AK/FileSystemPath.h @@ -47,7 +47,7 @@ public: const Vector& parts() const { return m_parts; } - bool has_extension(StringView) const; + bool has_extension(const StringView&) const; private: void canonicalize(); diff --git a/AK/String.cpp b/AK/String.cpp index 14f03c3f380..e8dc9f9c05d 100644 --- a/AK/String.cpp +++ b/AK/String.cpp @@ -278,9 +278,9 @@ bool String::starts_with(char ch) const return characters()[0] == ch; } -bool String::ends_with(const StringView& str) const +bool String::ends_with(const StringView& str, CaseSensitivity case_sensitivity) const { - return StringUtils::ends_with(*this, str); + return StringUtils::ends_with(*this, str, case_sensitivity); } bool String::ends_with(char ch) const diff --git a/AK/String.h b/AK/String.h index e882b8919fc..f0b27ce6585 100644 --- a/AK/String.h +++ b/AK/String.h @@ -146,7 +146,7 @@ public: ConstIterator end() const { return begin() + length(); } bool starts_with(const StringView&) const; - bool ends_with(const StringView&) const; + bool ends_with(const StringView&, CaseSensitivity = CaseSensitivity::CaseSensitive) const; bool starts_with(char) const; bool ends_with(char) const; diff --git a/AK/StringUtils.cpp b/AK/StringUtils.cpp index 09e46617100..ee6b2a2962c 100644 --- a/AK/StringUtils.cpp +++ b/AK/StringUtils.cpp @@ -196,7 +196,7 @@ bool equals_ignoring_case(const StringView& a, const StringView& b) return true; } -bool ends_with(const StringView& str, const StringView& end) +bool ends_with(const StringView& str, const StringView& end, CaseSensitivity case_sensitivity) { if (end.is_empty()) return true; @@ -204,7 +204,19 @@ bool ends_with(const StringView& str, const StringView& end) return false; if (end.length() > str.length()) return false; - return !memcmp(str.characters_without_null_termination() + (str.length() - end.length()), end.characters_without_null_termination(), end.length()); + + if (case_sensitivity == CaseSensitivity::CaseSensitive) + return !memcmp(str.characters_without_null_termination() + (str.length() - end.length()), end.characters_without_null_termination(), end.length()); + + auto str_chars = str.characters_without_null_termination(); + auto end_chars = end.characters_without_null_termination(); + + size_t si = str.length() - end.length(); + for (size_t ei = 0; ei < end.length(); ++si, ++ei) { + if (to_lowercase(str_chars[si]) != to_lowercase(end_chars[ei])) + return false; + } + return true; } } diff --git a/AK/StringUtils.h b/AK/StringUtils.h index 5ba124f9dee..e1326b10f62 100644 --- a/AK/StringUtils.h +++ b/AK/StringUtils.h @@ -43,8 +43,7 @@ int convert_to_int(const StringView&, bool& ok); unsigned convert_to_uint(const StringView&, bool& ok); unsigned convert_to_uint_from_hex(const StringView&, bool& ok); bool equals_ignoring_case(const StringView&, const StringView&); -bool ends_with(const StringView& str, const StringView& end); - +bool ends_with(const StringView& a, const StringView& b, CaseSensitivity); } } diff --git a/AK/Tests/TestFileSystemPath.cpp b/AK/Tests/TestFileSystemPath.cpp index 2c8ec62d13d..2cb16079390 100644 --- a/AK/Tests/TestFileSystemPath.cpp +++ b/AK/Tests/TestFileSystemPath.cpp @@ -85,4 +85,36 @@ TEST_CASE(relative_paths) } } +TEST_CASE(has_extension) +{ + { + FileSystemPath path("/tmp/simple.png"); + EXPECT(path.has_extension(".png")); + EXPECT(path.has_extension(".pnG")); + EXPECT(path.has_extension(".PNG")); + } + + { + FileSystemPath path("/TMP/SIMPLE.PNG"); + EXPECT(path.has_extension(".png")); + EXPECT(path.has_extension(".pnG")); + EXPECT(path.has_extension(".PNG")); + } + + { + FileSystemPath path(".png"); + EXPECT(path.has_extension(".png")); + } + + { + FileSystemPath path; + EXPECT_EQ(path.has_extension(".png"), false); + } + + { + FileSystemPath path("png"); + EXPECT_EQ(path.has_extension(".png"), false); + } +} + TEST_MAIN(FileSystemPath)