AK: Enhance String::contains to allow case-insensitive searches

This commit is contained in:
Tom 2020-10-20 15:07:03 -06:00 committed by Andreas Kling
parent 6413acd78c
commit 25e7225782
7 changed files with 54 additions and 8 deletions

View file

@ -275,11 +275,9 @@ bool String::matches(const StringView& mask, CaseSensitivity case_sensitivity) c
return StringUtils::matches(*this, mask, case_sensitivity);
}
bool String::contains(const String& needle) const
bool String::contains(const StringView& needle, CaseSensitivity case_sensitivity) const
{
if (is_null() || needle.is_null())
return false;
return strstr(characters(), needle.characters());
return StringUtils::contains(*this, needle, case_sensitivity);
}
Optional<size_t> String::index_of(const String& needle, size_t start) const

View file

@ -128,7 +128,7 @@ public:
bool equals_ignoring_case(const StringView&) const;
bool contains(const String&) const;
bool contains(const StringView&, CaseSensitivity = CaseSensitivity::CaseSensitive) const;
Optional<size_t> index_of(const String&, size_t start = 0) const;
Vector<String> split_limit(char separator, size_t limit, bool keep_empty = false) const;

View file

@ -227,6 +227,34 @@ bool starts_with(const StringView& str, const StringView& start, CaseSensitivity
return true;
}
bool contains(const StringView& str, const StringView& needle, CaseSensitivity case_sensitivity)
{
if (str.is_null() || needle.is_null() || str.is_empty() || needle.length() > str.length())
return false;
if (needle.is_empty())
return true;
auto str_chars = str.characters_without_null_termination();
auto needle_chars = needle.characters_without_null_termination();
if (case_sensitivity == CaseSensitivity::CaseSensitive)
return memmem(str_chars, str.length(), needle_chars, needle.length()) != nullptr;
auto needle_first = to_lowercase(needle_chars[0]);
size_t slen = str.length() - needle.length();
for (size_t si = 0; si < slen; si++) {
if (to_lowercase(str_chars[si]) != needle_first)
continue;
size_t ni = 1;
while (ni < needle.length()) {
if (to_lowercase(str_chars[si + ni]) != to_lowercase(needle_chars[ni]))
break;
ni++;
}
if (ni == needle.length())
return true;
}
return false;
}
StringView trim_whitespace(const StringView& str, TrimMode mode)
{
auto is_whitespace_character = [](char ch) -> bool {

View file

@ -51,6 +51,7 @@ Optional<unsigned> convert_to_uint_from_hex(const StringView&);
bool equals_ignoring_case(const StringView&, const StringView&);
bool ends_with(const StringView& a, const StringView& b, CaseSensitivity);
bool starts_with(const StringView&, const StringView&, CaseSensitivity);
bool contains(const StringView&, const StringView&, CaseSensitivity);
StringView trim_whitespace(const StringView&, TrimMode mode);
}

View file

@ -178,9 +178,9 @@ bool StringView::contains(char needle) const
return false;
}
bool StringView::contains(const StringView& needle) const
bool StringView::contains(const StringView& needle, CaseSensitivity case_sensitivity) const
{
return memmem(m_characters, m_length, needle.m_characters, needle.m_length) != nullptr;
return StringUtils::contains(*this, needle, case_sensitivity);
}
bool StringView::equals_ignoring_case(const StringView& other) const

View file

@ -88,7 +88,7 @@ public:
bool ends_with(char) const;
bool matches(const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;
bool contains(char) const;
bool contains(const StringView&) const;
bool contains(const StringView&, CaseSensitivity = CaseSensitivity::CaseSensitive) const;
bool equals_ignoring_case(const StringView& other) const;
StringView trim_whitespace(TrimMode mode = TrimMode::Both) const { return StringUtils::trim_whitespace(*this, mode); }

View file

@ -175,4 +175,23 @@ TEST_CASE(starts_with)
EXPECT(!AK::StringUtils::starts_with(test_string, "abc", CaseSensitivity::CaseSensitive));
}
TEST_CASE(contains)
{
String test_string = "ABCDEFABCXYZ";
EXPECT(AK::StringUtils::contains(test_string, "ABC", CaseSensitivity::CaseSensitive));
EXPECT(AK::StringUtils::contains(test_string, "ABC", CaseSensitivity::CaseInsensitive));
EXPECT(AK::StringUtils::contains(test_string, "AbC", CaseSensitivity::CaseInsensitive));
EXPECT(AK::StringUtils::contains(test_string, "BCX", CaseSensitivity::CaseSensitive));
EXPECT(AK::StringUtils::contains(test_string, "BCX", CaseSensitivity::CaseInsensitive));
EXPECT(AK::StringUtils::contains(test_string, "BcX", CaseSensitivity::CaseInsensitive));
EXPECT(!AK::StringUtils::contains(test_string, "EFG", CaseSensitivity::CaseSensitive));
EXPECT(!AK::StringUtils::contains(test_string, "EfG", CaseSensitivity::CaseInsensitive));
EXPECT(AK::StringUtils::contains(test_string, "", CaseSensitivity::CaseSensitive));
EXPECT(AK::StringUtils::contains(test_string, "", CaseSensitivity::CaseInsensitive));
EXPECT(!AK::StringUtils::contains("", test_string, CaseSensitivity::CaseSensitive));
EXPECT(!AK::StringUtils::contains("", test_string, CaseSensitivity::CaseInsensitive));
EXPECT(!AK::StringUtils::contains(test_string, "L", CaseSensitivity::CaseSensitive));
EXPECT(!AK::StringUtils::contains(test_string, "L", CaseSensitivity::CaseInsensitive));
}
TEST_MAIN(StringUtils)