diff --git a/src/tests/text.t.cpp b/src/tests/text.t.cpp index c13813e35..f216c3d80 100644 --- a/src/tests/text.t.cpp +++ b/src/tests/text.t.cpp @@ -34,7 +34,7 @@ Context context; //////////////////////////////////////////////////////////////////////////////// int main (int argc, char** argv) { - UnitTest t (147); + UnitTest t (176); // void wrapText (std::vector & lines, const std::string& text, const int width) std::string text = "This is a test of the line wrapping code."; @@ -305,8 +305,58 @@ int main (int argc, char** argv) t.ok (isWordEnd (text, 11), "isWordEnd (\"Hello, world.\", 11) -> true"); t.notok (isWordEnd (text, 12), "isWordEnd (\"Hello, world.\", 12) -> false"); + // bool compare (const std::string&, const std::string&, bool caseless = false); + // Make sure degenerate cases are handled. + t.ok (compare ("", ""), "'' == ''"); + t.notok (compare ("foo", ""), "foo != ''"); + t.notok (compare ("", "foo"), "'' != foo"); + + // Make sure the default is case-sensitive. + t.ok (compare ("foo", "foo"), "foo == foo"); + t.notok (compare ("foo", "FOO"), "foo != foo"); + + // Test case-sensitive. + t.notok (compare ("foo", "xx", false), "foo != xx"); + + t.ok (compare ("foo", "foo", false), "foo == foo"); + t.notok (compare ("foo", "FOO", false), "foo != FOO"); + t.notok (compare ("FOO", "foo", false), "FOO != foo"); + t.ok (compare ("FOO", "FOO", false), "FOO == FOO"); + + // Test case-insensitive. + t.notok (compare ("foo", "xx", true), "foo != foo (caseless)"); + + t.ok (compare ("foo", "foo", true), "foo == foo (caseless)"); + t.ok (compare ("foo", "FOO", true), "foo == FOO (caseless)"); + t.ok (compare ("FOO", "foo", true), "FOO == foo (caseless)"); + t.ok (compare ("FOO", "FOO", true), "FOO == FOO (caseless)"); + + // std::string::size_type find (const std::string&, const std::string&, bool caseless = false); + // Make sure degenerate cases are handled. + t.is ((int) find ("foo", ""), (int) 0, "foo !contains ''"); + t.is ((int) find ("", "foo"), (int) std::string::npos, "'' !contains foo"); + + // Make sure the default is case-sensitive. + t.is ((int) find ("foo", "fo"), 0, "foo contains fo"); + t.is ((int) find ("foo", "FO"), (int) std::string::npos, "foo !contains fo"); + + // Test case-sensitive. + t.is ((int) find ("foo", "xx", false), (int) std::string::npos, "foo !contains xx"); + + t.is ((int) find ("foo", "fo", false), 0, "foo contains fo"); + t.is ((int) find ("foo", "FO", false), (int) std::string::npos, "foo !contains fo"); + t.is ((int) find ("FOO", "fo", false), (int) std::string::npos, "foo !contains fo"); + t.is ((int) find ("FOO", "FO", false), 0, "foo contains fo"); + + // Test case-insensitive. + t.is ((int) find ("foo", "xx", true), (int) std::string::npos, "foo !contains xx (caseless)"); + + t.is ((int) find ("foo", "fo", true), 0, "foo contains fo (caseless)"); + t.is ((int) find ("foo", "FO", true), 0, "foo contains FO (caseless)"); + t.is ((int) find ("FOO", "fo", true), 0, "FOO contains fo (caseless)"); + t.is ((int) find ("FOO", "FO", true), 0, "FOO contains FO (caseless)"); + return 0; } //////////////////////////////////////////////////////////////////////////////// - diff --git a/src/text.cpp b/src/text.cpp index 1da58de17..56019c645 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "Context.h" #include "util.h" @@ -462,3 +463,59 @@ bool isWordEnd (const std::string& input, std::string::size_type pos) } //////////////////////////////////////////////////////////////////////////////// +bool compare ( + const std::string& left, + const std::string& right, + bool caseless /*= false*/) +{ + // Use strcasecmp if required. + if (caseless) + return strcasecmp (left.c_str (), right.c_str ()) == 0 ? true : false; + + // Otherwise, just use std::string::operator==. + return left == right; +} + +//////////////////////////////////////////////////////////////////////////////// +std::string::size_type find ( + const std::string& text, + const std::string& pattern, + bool caseless /*= false*/) +{ + // Implement a caseless find, which is really just a loop withing a loop, + // comparing lower-case versions of each character in turn. + if (caseless) + { + // Handle empty pattern. + const char* p = pattern.c_str (); + size_t len = pattern.length (); + if (len == 0) + return 0; + + // Evaluate these once, for performance reasons. + const char* t = text.c_str (); + const char* start = t; + const char* end = start + text.size (); + + for (; t < end - len; ++t) + { + int diff; + for (size_t i = 0; i < len; ++i) + if ((diff = tolower (t[i]) - tolower (p[i]))) + break; + + // diff == 0 means there was no break from the loop, which only occurs + // when a difference is detected. Therefore, the loop terminated, and + // diff is zero. + if (diff == 0) + return t - start; + } + + return std::string::npos; + } + + // Otherwise, just use std::string::find. + return text.find (pattern); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/text.h b/src/text.h index ef56304ad..ba310e6b1 100644 --- a/src/text.h +++ b/src/text.h @@ -54,6 +54,8 @@ bool noSpaces (const std::string&); bool noVerticalSpace (const std::string&); bool isWordStart (const std::string&, std::string::size_type); bool isWordEnd (const std::string&, std::string::size_type); +bool compare (const std::string&, const std::string&, bool caseless = false); +std::string::size_type find (const std::string&, const std::string&, bool caseless = false); #endif ////////////////////////////////////////////////////////////////////////////////