diff --git a/Makefile.am b/Makefile.am index ffafc50fe..6a7e7138b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -534,6 +534,8 @@ libevent_a_SOURCES = \ # UTF-8 library libicu_a_SOURCES = \ + src/lib/icu/CaseFold.cxx src/lib/icu/CaseFold.hxx \ + src/lib/icu/Compare.cxx src/lib/icu/Compare.hxx \ src/lib/icu/Collate.cxx src/lib/icu/Collate.hxx \ src/lib/icu/Converter.cxx src/lib/icu/Converter.hxx diff --git a/NEWS b/NEWS index 099a59694..d4d901027 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ ver 0.20.11 (not yet released) - curl: support Content-Type application/xml * decoder - ffmpeg: more reliable song duration +* fix case insensitive search without libicu ver 0.20.10 (2017/08/24) * decoder diff --git a/configure.ac b/configure.ac index 19fc4ce84..7b8903b3b 100644 --- a/configure.ac +++ b/configure.ac @@ -241,6 +241,7 @@ AC_CHECK_FUNCS(getpwnam_r getpwuid_r) AC_CHECK_FUNCS(initgroups) AC_CHECK_FUNCS(fnmatch) AC_CHECK_FUNCS(strndup) +AC_CHECK_FUNCS(strcasestr) if test x$host_is_linux = xyes; then MPD_OPTIONAL_FUNC(eventfd, eventfd, USE_EVENTFD) diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx index 2bdb5b2fb..3bb8894ef 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.cxx @@ -28,7 +28,7 @@ #include "util/ASCII.hxx" #include "util/TimeParser.hxx" #include "util/UriUtil.hxx" -#include "lib/icu/Collate.hxx" +#include "lib/icu/CaseFold.hxx" #include @@ -58,17 +58,10 @@ locate_parse_type(const char *str) noexcept return tag_name_parse_i(str); } -static AllocatedString<> -ImportString(const char *p, bool fold_case) -{ - return fold_case - ? IcuCaseFold(p) - : AllocatedString<>::Duplicate(p); -} - SongFilter::Item::Item(unsigned _tag, const char *_value, bool _fold_case) - :tag(_tag), fold_case(_fold_case), - value(ImportString(_value, _fold_case)) + :tag(_tag), + value(AllocatedString<>::Duplicate(_value)), + fold_case(_fold_case ? IcuCompare(value.c_str()) : IcuCompare()) { } @@ -89,9 +82,7 @@ SongFilter::Item::StringMatch(const char *s) const noexcept assert(tag != LOCATE_TAG_MODIFIED_SINCE); if (fold_case) { - const auto folded = IcuCaseFold(s); - assert(!folded.IsNull()); - return StringFind(folded.c_str(), value.c_str()) != nullptr; + return fold_case.IsIn(s); } else { return StringIsEqual(s, value.c_str()); } diff --git a/src/SongFilter.hxx b/src/SongFilter.hxx index db92f2338..b18d7a184 100644 --- a/src/SongFilter.hxx +++ b/src/SongFilter.hxx @@ -20,6 +20,7 @@ #ifndef MPD_SONG_FILTER_HXX #define MPD_SONG_FILTER_HXX +#include "lib/icu/Compare.hxx" #include "util/AllocatedString.hxx" #include "Compiler.h" @@ -48,10 +49,13 @@ public: class Item { uint8_t tag; - bool fold_case; - AllocatedString<> value; + /** + * This value is only set if case folding is enabled. + */ + IcuCompare fold_case; + /** * For #LOCATE_TAG_MODIFIED_SINCE */ diff --git a/src/db/Selection.cxx b/src/db/Selection.cxx index bab9c9de8..c25bf99f1 100644 --- a/src/db/Selection.cxx +++ b/src/db/Selection.cxx @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "Selection.hxx" #include "SongFilter.hxx" diff --git a/src/lib/icu/CaseFold.cxx b/src/lib/icu/CaseFold.cxx new file mode 100644 index 000000000..ccdf998d5 --- /dev/null +++ b/src/lib/icu/CaseFold.cxx @@ -0,0 +1,102 @@ +/* + * Copyright 2003-2017 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CaseFold.hxx" + +#ifdef HAVE_ICU_CASE_FOLD + +#include "util/AllocatedString.hxx" + +#ifdef HAVE_ICU +#include "Util.hxx" +#include "util/AllocatedArray.hxx" +#include "util/ConstBuffer.hxx" + +#include +#include +#else +#include +#include +#endif + +#ifdef WIN32 +#include "Win32.hxx" +#include +#endif + +#include +#include + +#include +#include + +AllocatedString<> +IcuCaseFold(const char *src) noexcept +try { +#ifdef HAVE_ICU +#if !CLANG_CHECK_VERSION(3,6) + /* disabled on clang due to -Wtautological-pointer-compare */ + assert(src != nullptr); +#endif + + const auto u = UCharFromUTF8(src); + if (u.IsNull()) + return AllocatedString<>::Duplicate(src); + + AllocatedArray folded(u.size() * 2u); + + UErrorCode error_code = U_ZERO_ERROR; + size_t folded_length = u_strFoldCase(folded.begin(), folded.size(), + u.begin(), u.size(), + U_FOLD_CASE_DEFAULT, + &error_code); + if (folded_length == 0 || error_code != U_ZERO_ERROR) + return AllocatedString<>::Duplicate(src); + + folded.SetSize(folded_length); + return UCharToUTF8({folded.begin(), folded.size()}); + +#elif defined(WIN32) + const auto u = MultiByteToWideChar(CP_UTF8, src); + + const int size = LCMapStringEx(LOCALE_NAME_INVARIANT, + LCMAP_SORTKEY|LINGUISTIC_IGNORECASE, + u.c_str(), -1, nullptr, 0, + nullptr, nullptr, 0); + if (size <= 0) + return AllocatedString<>::Duplicate(src); + + std::unique_ptr buffer(new wchar_t[size]); + if (LCMapStringEx(LOCALE_NAME_INVARIANT, + LCMAP_SORTKEY|LINGUISTIC_IGNORECASE, + u.c_str(), -1, buffer.get(), size, + nullptr, nullptr, 0) <= 0) + return AllocatedString<>::Duplicate(src); + + return WideCharToMultiByte(CP_UTF8, buffer.get()); + +#else +#error not implemented +#endif +} catch (const std::runtime_error &) { + return AllocatedString<>::Duplicate(src); +} + +#endif /* HAVE_ICU_CASE_FOLD */ diff --git a/src/lib/icu/CaseFold.hxx b/src/lib/icu/CaseFold.hxx new file mode 100644 index 000000000..eaeaf5689 --- /dev/null +++ b/src/lib/icu/CaseFold.hxx @@ -0,0 +1,38 @@ +/* + * Copyright 2003-2017 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICU_CASE_FOLD_HXX +#define MPD_ICU_CASE_FOLD_HXX + +#include "check.h" + +#if defined(HAVE_ICU) || defined(_WIN32) +#define HAVE_ICU_CASE_FOLD + +#include "Compiler.h" + +template class AllocatedString; + +gcc_nonnull_all +AllocatedString +IcuCaseFold(const char *src) noexcept; + +#endif + +#endif diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx index abb4b709c..f16cba597 100644 --- a/src/lib/icu/Collate.cxx +++ b/src/lib/icu/Collate.cxx @@ -23,8 +23,6 @@ #ifdef HAVE_ICU #include "Util.hxx" -#include "util/AllocatedArray.hxx" -#include "util/ConstBuffer.hxx" #include "util/RuntimeError.hxx" #include @@ -141,70 +139,3 @@ IcuCollate(const char *a, const char *b) noexcept return strcoll(a, b); #endif } - -AllocatedString<> -IcuCaseFold(const char *src) -try { -#ifdef HAVE_ICU - assert(collator != nullptr); -#if !CLANG_CHECK_VERSION(3,6) - /* disabled on clang due to -Wtautological-pointer-compare */ - assert(src != nullptr); -#endif - - const auto u = UCharFromUTF8(src); - if (u.IsNull()) - return AllocatedString<>::Duplicate(src); - - AllocatedArray folded(u.size() * 2u); - - UErrorCode error_code = U_ZERO_ERROR; - size_t folded_length = u_strFoldCase(folded.begin(), folded.size(), - u.begin(), u.size(), - U_FOLD_CASE_DEFAULT, - &error_code); - if (folded_length == 0 || error_code != U_ZERO_ERROR) - return AllocatedString<>::Duplicate(src); - - folded.SetSize(folded_length); - return UCharToUTF8({folded.begin(), folded.size()}); - -#elif defined(WIN32) - const auto u = MultiByteToWideChar(CP_UTF8, src); - - const int size = LCMapStringEx(LOCALE_NAME_INVARIANT, - LCMAP_SORTKEY|LINGUISTIC_IGNORECASE, - u.c_str(), -1, nullptr, 0, - nullptr, nullptr, 0); - if (size <= 0) - return AllocatedString<>::Duplicate(src); - - std::unique_ptr buffer(new wchar_t[size]); - if (LCMapStringEx(LOCALE_NAME_INVARIANT, - LCMAP_SORTKEY|LINGUISTIC_IGNORECASE, - u.c_str(), -1, buffer.get(), size, - nullptr, nullptr, 0) <= 0) - return AllocatedString<>::Duplicate(src); - - return WideCharToMultiByte(CP_UTF8, buffer.get()); - -#else - size_t size = strlen(src) + 1; - std::unique_ptr buffer(new char[size]); - size_t nbytes = strxfrm(buffer.get(), src, size); - if (nbytes >= size) { - /* buffer too small - reallocate and try again */ - buffer.reset(); - size = nbytes + 1; - buffer.reset(new char[size]); - nbytes = strxfrm(buffer.get(), src, size); - } - - assert(nbytes < size); - assert(buffer[nbytes] == 0); - - return AllocatedString<>::Donate(buffer.release()); -#endif -} catch (const std::runtime_error &) { - return AllocatedString<>::Duplicate(src); -} diff --git a/src/lib/icu/Collate.hxx b/src/lib/icu/Collate.hxx index d6cfcb764..d22fa6870 100644 --- a/src/lib/icu/Collate.hxx +++ b/src/lib/icu/Collate.hxx @@ -23,8 +23,6 @@ #include "check.h" #include "Compiler.h" -template class AllocatedString; - /** * Throws #std::runtime_error on error. */ @@ -38,8 +36,4 @@ gcc_pure gcc_nonnull_all int IcuCollate(const char *a, const char *b) noexcept; -gcc_nonnull_all -AllocatedString -IcuCaseFold(const char *src); - #endif diff --git a/src/lib/icu/Compare.cxx b/src/lib/icu/Compare.cxx new file mode 100644 index 000000000..21b3230bb --- /dev/null +++ b/src/lib/icu/Compare.cxx @@ -0,0 +1,66 @@ +/* + * Copyright 2003-2017 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Compare.hxx" +#include "CaseFold.hxx" +#include "util/StringAPI.hxx" + +#include + +#ifdef HAVE_ICU_CASE_FOLD + +IcuCompare::IcuCompare(const char *_needle) noexcept + :needle(IcuCaseFold(_needle)) {} + +#else + +IcuCompare::IcuCompare(const char *_needle) noexcept + :needle(AllocatedString<>::Duplicate(_needle)) {} + +#endif + +bool +IcuCompare::operator==(const char *haystack) const noexcept +{ +#ifdef HAVE_ICU_CASE_FOLD + return StringIsEqual(IcuCaseFold(haystack).c_str(), needle.c_str()); +#else + return strcasecmp(haystack, needle.c_str()); +#endif +} + +bool +IcuCompare::IsIn(const char *haystack) const noexcept +{ +#ifdef HAVE_ICU_CASE_FOLD + return StringFind(IcuCaseFold(haystack).c_str(), + needle.c_str()) != nullptr; +#elif defined(HAVE_STRCASESTR) + return strcasestr(haystack, needle.c_str()) != nullptr; +#else + /* poor man's strcasestr() */ + for (const size_t length = strlen(needle.c_str()); + *haystack != 0; ++haystack) + if (strncasecmp(haystack, needle.c_str(), length) == 0) + return true; + + return false; +#endif +} diff --git a/src/lib/icu/Compare.hxx b/src/lib/icu/Compare.hxx new file mode 100644 index 000000000..9ee2e6848 --- /dev/null +++ b/src/lib/icu/Compare.hxx @@ -0,0 +1,55 @@ +/* + * Copyright 2003-2017 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICU_COMPARE_HXX +#define MPD_ICU_COMPARE_HXX + +#include "check.h" +#include "Compiler.h" +#include "util/AllocatedString.hxx" + +/** + * This class can compare one string ("needle") with lots of other + * strings ("haystacks") efficiently, ignoring case. With some + * configurations, it can prepare a case-folded version of the needle. + */ +class IcuCompare { + AllocatedString<> needle; + +public: + IcuCompare():needle(nullptr) {} + + explicit IcuCompare(const char *needle) noexcept; + + IcuCompare(IcuCompare &&) = default; + IcuCompare &operator=(IcuCompare &&) = default; + + gcc_pure + operator bool() const noexcept { + return !needle.IsNull(); + } + + gcc_pure + bool operator==(const char *haystack) const noexcept; + + gcc_pure + bool IsIn(const char *haystack) const noexcept; +}; + +#endif