From 49524d74182bb506ff09ec2be2225e5ae114b0d3 Mon Sep 17 00:00:00 2001 From: Zbigniew Skowron Date: Sun, 15 Aug 2021 14:25:47 +0200 Subject: [PATCH] Fixed bugs in String and StringView functions (Replace, Compare, etc.). --- Source/Engine/Core/Types/String.cpp | 8 ++++++-- Source/Engine/Core/Types/String.h | 26 ++++++++++++++------------ Source/Engine/Core/Types/StringView.h | 20 +++++++++++--------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Source/Engine/Core/Types/String.cpp b/Source/Engine/Core/Types/String.cpp index 7919d513d..2ea3c7dc5 100644 --- a/Source/Engine/Core/Types/String.cpp +++ b/Source/Engine/Core/Types/String.cpp @@ -226,7 +226,9 @@ bool String::IsANSI() const bool String::StartsWith(const StringView& prefix, StringSearchCase searchCase) const { - if (prefix.IsEmpty() || prefix.Length() > Length()) + if (prefix.IsEmpty()) + return true; + if (prefix.Length() > Length()) return false; if (searchCase == StringSearchCase::IgnoreCase) return !StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length()); @@ -235,7 +237,9 @@ bool String::StartsWith(const StringView& prefix, StringSearchCase searchCase) c bool String::EndsWith(const StringView& suffix, StringSearchCase searchCase) const { - if (suffix.IsEmpty() || suffix.Length() > Length()) + if (suffix.IsEmpty()) + return true; + if (suffix.Length() > Length()) return false; if (searchCase == StringSearchCase::IgnoreCase) return !StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix); diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 0a62c516f..3485a1093 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -65,10 +65,11 @@ public: /// /// Lexicographically tests how this string compares to the other given string. + /// In case sensitive mode 'A' is less than 'a'. /// /// The another string test against. /// The case sensitivity mode. - /// 0 if equal, -1 if less than, 1 if greater than. + /// 0 if equal, negative number if less than, positive number if greater than. int32 Compare(const StringBase& str, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const { if (searchCase == StringSearchCase::CaseSensitive) @@ -352,7 +353,7 @@ public: bool StartsWith(T c, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const { const int32 length = Length(); - if (searchCase == StringSearchCase::IgnoreCase) + if (searchCase == StringSearchCase::CaseSensitive) return length > 0 && _data[0] == c; return length > 0 && StringUtils::ToLower(_data[0]) == StringUtils::ToLower(c); } @@ -360,14 +361,16 @@ public: bool EndsWith(T c, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const { const int32 length = Length(); - if (searchCase == StringSearchCase::IgnoreCase) + if (searchCase == StringSearchCase::CaseSensitive) return length > 0 && _data[length - 1] == c; return length > 0 && StringUtils::ToLower(_data[length - 1]) == StringUtils::ToLower(c); } bool StartsWith(const StringBase& prefix, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const { - if (prefix.IsEmpty() || Length() < prefix.Length()) + if (prefix.IsEmpty()) + return true; + if (Length() < prefix.Length()) return false; if (searchCase == StringSearchCase::IgnoreCase) return StringUtils::CompareIgnoreCase(this->GetText(), *prefix, prefix.Length()) == 0; @@ -376,7 +379,9 @@ public: bool EndsWith(const StringBase& suffix, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const { - if (suffix.IsEmpty() || Length() < suffix.Length()) + if (suffix.IsEmpty()) + return true; + if (Length() < suffix.Length()) return false; if (searchCase == StringSearchCase::IgnoreCase) return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0; @@ -429,22 +434,19 @@ public: /// /// Replaces all occurences of searchText within current string with replacementText. /// - /// String to search for. If empty or null no replacements are done. - /// Length of searchText. + /// String to search for. + /// Length of searchText. Must be greater than zero. /// String to replace with. Null is treated as empty string. /// Length of replacementText. - /// Number of replacements made. (In case-sensitive mode if search text and replacement text are equal no replacements are done, and zero is returned.) + /// Number of replacements made (in other words number of occurences of searchText). int32 Replace(const T* searchText, int32 searchTextLength, const T* replacementText, int32 replacementTextLength, StringSearchCase searchCase = StringSearchCase::CaseSensitive) { if (!HasChars()) return 0; if (searchTextLength == 0) - return 0; - - // If we are doing case sensitive search and replacement text is equal to search text, we do nothing. - if ((searchCase == StringSearchCase::CaseSensitive) && (searchTextLength == replacementTextLength) && (StringUtils::Compare(searchText, replacementText) == 0)) { + ASSERT(false); // Empty search text never makes sense, and is always sign of a bug in calling code. return 0; } diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index f0d608493..74f8e398e 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -54,21 +54,23 @@ public: /// /// Lexicographically tests how this string compares to the other given string. + /// In case sensitive mode 'A' is less than 'a'. /// /// The another string test against. /// The case sensitivity mode. - /// 0 if equal, -1 if less than, 1 if greater than. + /// 0 if equal, negative number if less than, positive number if greater than. int32 Compare(const StringViewBase& str, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const { - const int32 lengthDiff = Length() - str.Length(); - if (lengthDiff != 0) - return lengthDiff; - if (Length() == 0) + const bool thisIsShorter = Length() < str.Length(); + const int32 minLength = thisIsShorter ? Length() : str.Length(); + const int32 prefixCompare = (searchCase == StringSearchCase::CaseSensitive) + ? StringUtils::Compare(this->GetNonTerminatedText(), str.GetNonTerminatedText(), minLength) + : StringUtils::CompareIgnoreCase(this->GetNonTerminatedText(), str.GetNonTerminatedText(), minLength); + if (prefixCompare != 0) + return prefixCompare; + if (Length() == str.Length()) return 0; - // We know here that both this StringView and str are not empty, and therefore Get() below are valid. - if (searchCase == StringSearchCase::CaseSensitive) - return StringUtils::Compare(this->Get(), str.Get(), Length()); - return StringUtils::CompareIgnoreCase(this->Get(), str.Get(), Length()); + return thisIsShorter ? -1 : 1; } public: