Add support for ignoring diacritics while searching and filtering lists

This commit is contained in:
Andrzej Rybczak
2017-03-29 21:38:14 +02:00
parent 29403d41ab
commit 66912d73da
14 changed files with 109 additions and 48 deletions

3
NEWS
View File

@@ -30,7 +30,8 @@ ncmpcpp-0.8 (????-??-??)
* Disable autocenter mode while searching and filtering. * Disable autocenter mode while searching and filtering.
* Added '--quiet' comand line argument that supresses messages shown at startup. * Added '--quiet' comand line argument that supresses messages shown at startup.
* Multiple songs in Media library are now added to playlist in the same order they are displayed. * Multiple songs in Media library are now added to playlist in the same order they are displayed.
* Added configuration options 'media_library_albums_split_by_date' that determines whether albums in media library should be split by date. * Added configuration option 'media_library_albums_split_by_date' that determines whether albums in media library should be split by date.
* Added configuration option 'ignore_diacritics' that allows for ignoring diacritics while searching (boost compiled with ICU support is required).
ncmpcpp-0.7.7 (2016-10-31) ncmpcpp-0.7.7 (2016-10-31)
* Fixed compilation on 32bit platforms. * Fixed compilation on 32bit platforms.

View File

@@ -108,7 +108,7 @@ dnl ====================
dnl = checking for icu = dnl = checking for icu =
dnl ==================== dnl ====================
AH_TEMPLATE([BOOST_REGEX_ICU], [boost.regex was compiled with ICU support]) AH_TEMPLATE([BOOST_REGEX_ICU], [boost.regex was compiled with ICU support])
PKG_CHECK_MODULES([ICU], [icu-uc], [ PKG_CHECK_MODULES([ICU], [icu-i18n icu-uc], [
old_CPPFLAGS="$CPPFLAGS" old_CPPFLAGS="$CPPFLAGS"
old_LIBS="$LIBS" old_LIBS="$LIBS"
AC_SUBST(ICU_CFLAGS) AC_SUBST(ICU_CFLAGS)

View File

@@ -421,11 +421,18 @@
#regular_expressions = perl #regular_expressions = perl
# #
## ##
## Note: If below is enabled, ncmpcpp will ignore leading "The" word while ## Note: if below is enabled, ncmpcpp will ignore leading "The" word while
## sorting items in browser, tags in media library, etc. ## sorting items in browser, tags in media library, etc.
## ##
#ignore_leading_the = no #ignore_leading_the = no
# #
##
## Note: if below is enabled, ncmpcpp will ignore diacritics while searching and
## filtering lists. This takes an effect only if boost was compiled with ICU
## support.
##
#ignore_diacritics = no
#
#block_search_constraints_change_if_items_found = yes #block_search_constraints_change_if_items_found = yes
# #
#mouse_support = yes #mouse_support = yes

View File

@@ -315,6 +315,9 @@ Type of currently used regular expressions.
.B ignore_leading_the = yes/no .B ignore_leading_the = yes/no
If enabled, word "the" at the beginning of tags/filenames/sort format will be ignored while sorting items. If enabled, word "the" at the beginning of tags/filenames/sort format will be ignored while sorting items.
.TP .TP
.B ignore_diacritics = yes/no
If enabled, diacritics in strings will be ignored while searching and filtering lists.
.TP
.B block_search_constraints_change_if_items_found = yes/no .B block_search_constraints_change_if_items_found = yes/no
If enabled, fields in Search engine above "Reset" button will be blocked after successful searching, otherwise they won't. If enabled, fields in Search engine above "Reset" button will be blocked after successful searching, otherwise they won't.
.TP .TP

View File

@@ -25,6 +25,8 @@
#ifdef BOOST_REGEX_ICU #ifdef BOOST_REGEX_ICU
# include <boost/regex/icu.hpp> # include <boost/regex/icu.hpp>
# include <unicode/errorcode.h>
# include <unicode/translit.h>
#else #else
# include <boost/regex.hpp> # include <boost/regex.hpp>
#endif // BOOST_REGEX_ICU #endif // BOOST_REGEX_ICU
@@ -32,6 +34,39 @@
#include <cassert> #include <cassert>
#include <iostream> #include <iostream>
#include "utility/functional.h"
namespace {
#ifdef BOOST_REGEX_ICU
struct StripDiacritics
{
static void convert(UnicodeString &s)
{
if (m_converter == nullptr)
{
ErrorCode result;
m_converter = Transliterator::createInstance(
"NFD; [:M:] Remove; NFC", UTRANS_FORWARD, result);
if (result.isFailure())
throw std::runtime_error(
"instantiation of transliterator instance failed with "
+ std::string(result.errorName()));
}
m_converter->transliterate(s);
}
private:
static Transliterator *m_converter;
};
Transliterator *StripDiacritics::m_converter;
#endif // BOOST_REGEX_ICU
}
namespace Regex { namespace Regex {
typedef typedef
@@ -43,31 +78,44 @@ typedef
Regex; Regex;
template <typename StringT> template <typename StringT>
inline Regex make(StringT &&s, boost::regex_constants::syntax_option_type flags) inline Regex make(StringT &&s,
boost::regex_constants::syntax_option_type flags)
{ {
return return
# ifdef BOOST_REGEX_ICU #ifdef BOOST_REGEX_ICU
boost::make_u32regex boost::make_u32regex
# else #else
boost::regex boost::regex
# endif // BOOST_REGEX_ICU #endif // BOOST_REGEX_ICU
(std::forward<StringT>(s), flags); (std::forward<StringT>(s), flags);
} }
template <typename StringT> template <typename CharT>
inline bool search(StringT &&s, const Regex &rx) inline bool search(const std::basic_string<CharT> &s,
const Regex &rx,
bool ignore_diacritics)
{ {
try { try {
return #ifdef BOOST_REGEX_ICU
# ifdef BOOST_REGEX_ICU if (ignore_diacritics)
boost::u32regex_search {
# else auto us = UnicodeString::fromUTF8(
boost::regex_search StringPiece(convertString<char, CharT>::apply(s)));
# endif // BOOST_REGEX_ICU StripDiacritics::convert(us);
(std::forward<StringT>(s), rx); return boost::u32regex_search(us, rx);
}
else
return boost::u32regex_search(s, rx);
#else
return boost::regex_search(s, rx);
#endif // BOOST_REGEX_ICU
} catch (std::out_of_range &e) { } catch (std::out_of_range &e) {
// Invalid UTF-8 sequence, ignore the string. // Invalid UTF-8 sequence, ignore the string.
std::cerr << "Regex::search: error while processing \"" << s << "\": " << e.what() << "\n"; std::cerr << "Regex::search: error while processing \""
<< s
<< "\": "
<< e.what()
<< "\n";
return false; return false;
} }
} }

View File

@@ -734,7 +734,7 @@ bool browserEntryMatcher(const Regex::Regex &rx, const MPD::Item &item, bool fil
{ {
if (isItemParentDirectory(item)) if (isItemParentDirectory(item))
return filter; return filter;
return Regex::search(itemToString(item), rx); return Regex::search(itemToString(item), rx, Config.ignore_diacritics);
} }
} }

View File

@@ -1086,19 +1086,19 @@ std::string SongToString(const MPD::Song &s)
bool TagEntryMatcher(const Regex::Regex &rx, const PrimaryTag &pt) bool TagEntryMatcher(const Regex::Regex &rx, const PrimaryTag &pt)
{ {
return Regex::search(pt.tag(), rx); return Regex::search(pt.tag(), rx, Config.ignore_diacritics);
} }
bool AlbumEntryMatcher(const Regex::Regex &rx, const NC::Menu<AlbumEntry>::Item &item, bool filter) bool AlbumEntryMatcher(const Regex::Regex &rx, const NC::Menu<AlbumEntry>::Item &item, bool filter)
{ {
if (item.isSeparator() || item.value().isAllTracksEntry()) if (item.isSeparator() || item.value().isAllTracksEntry())
return filter; return filter;
return Regex::search(AlbumToString(item.value()), rx); return Regex::search(AlbumToString(item.value()), rx, Config.ignore_diacritics);
} }
bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s) bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s)
{ {
return Regex::search(SongToString(s), rx); return Regex::search(SongToString(s), rx, Config.ignore_diacritics);
} }
bool MoveToTag(NC::Menu<PrimaryTag> &tags, const std::string &primary_tag) bool MoveToTag(NC::Menu<PrimaryTag> &tags, const std::string &primary_tag)

View File

@@ -353,7 +353,7 @@ std::string songToString(const MPD::Song &s)
bool playlistEntryMatcher(const Regex::Regex &rx, const MPD::Song &s) bool playlistEntryMatcher(const Regex::Regex &rx, const MPD::Song &s)
{ {
return Regex::search(songToString(s), rx); return Regex::search(songToString(s), rx, Config.ignore_diacritics);
} }
} }

View File

@@ -558,12 +558,12 @@ std::string SongToString(const MPD::Song &s)
bool PlaylistEntryMatcher(const Regex::Regex &rx, const MPD::Playlist &playlist) bool PlaylistEntryMatcher(const Regex::Regex &rx, const MPD::Playlist &playlist)
{ {
return Regex::search(playlist.path(), rx); return Regex::search(playlist.path(), rx, Config.ignore_diacritics);
} }
bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s) bool SongEntryMatcher(const Regex::Regex &rx, const MPD::Song &s)
{ {
return Regex::search(SongToString(s), rx); return Regex::search(SongToString(s), rx, Config.ignore_diacritics);
} }
boost::optional<size_t> GetSongIndexInPlaylist(MPD::Playlist playlist, const MPD::Song &song) boost::optional<size_t> GetSongIndexInPlaylist(MPD::Playlist playlist, const MPD::Song &song)

View File

@@ -517,36 +517,36 @@ void SearchEngine::Search()
{ {
if (!rx[0].empty()) if (!rx[0].empty())
any_found = any_found =
Regex::search(s->getArtist(), rx[0]) Regex::search(s->getArtist(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getAlbumArtist(), rx[0]) || Regex::search(s->getAlbumArtist(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getTitle(), rx[0]) || Regex::search(s->getTitle(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getAlbum(), rx[0]) || Regex::search(s->getAlbum(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getName(), rx[0]) || Regex::search(s->getName(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getComposer(), rx[0]) || Regex::search(s->getComposer(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getPerformer(), rx[0]) || Regex::search(s->getPerformer(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getGenre(), rx[0]) || Regex::search(s->getGenre(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getDate(), rx[0]) || Regex::search(s->getDate(), rx[0], Config.ignore_diacritics)
|| Regex::search(s->getComment(), rx[0]); || Regex::search(s->getComment(), rx[0], Config.ignore_diacritics);
if (found && !rx[1].empty()) if (found && !rx[1].empty())
found = Regex::search(s->getArtist(), rx[1]); found = Regex::search(s->getArtist(), rx[1], Config.ignore_diacritics);
if (found && !rx[2].empty()) if (found && !rx[2].empty())
found = Regex::search(s->getAlbumArtist(), rx[2]); found = Regex::search(s->getAlbumArtist(), rx[2], Config.ignore_diacritics);
if (found && !rx[3].empty()) if (found && !rx[3].empty())
found = Regex::search(s->getTitle(), rx[3]); found = Regex::search(s->getTitle(), rx[3], Config.ignore_diacritics);
if (found && !rx[4].empty()) if (found && !rx[4].empty())
found = Regex::search(s->getAlbum(), rx[4]); found = Regex::search(s->getAlbum(), rx[4], Config.ignore_diacritics);
if (found && !rx[5].empty()) if (found && !rx[5].empty())
found = Regex::search(s->getName(), rx[5]); found = Regex::search(s->getName(), rx[5], Config.ignore_diacritics);
if (found && !rx[6].empty()) if (found && !rx[6].empty())
found = Regex::search(s->getComposer(), rx[6]); found = Regex::search(s->getComposer(), rx[6], Config.ignore_diacritics);
if (found && !rx[7].empty()) if (found && !rx[7].empty())
found = Regex::search(s->getPerformer(), rx[7]); found = Regex::search(s->getPerformer(), rx[7], Config.ignore_diacritics);
if (found && !rx[8].empty()) if (found && !rx[8].empty())
found = Regex::search(s->getGenre(), rx[8]); found = Regex::search(s->getGenre(), rx[8], Config.ignore_diacritics);
if (found && !rx[9].empty()) if (found && !rx[9].empty())
found = Regex::search(s->getDate(), rx[9]); found = Regex::search(s->getDate(), rx[9], Config.ignore_diacritics);
if (found && !rx[10].empty()) if (found && !rx[10].empty())
found = Regex::search(s->getComment(), rx[10]); found = Regex::search(s->getComment(), rx[10], Config.ignore_diacritics);
} }
else // match only if values are equal else // match only if values are equal
{ {
@@ -616,7 +616,7 @@ bool SEItemEntryMatcher(const Regex::Regex &rx, const NC::Menu<SEItem>::Item &it
{ {
if (item.isSeparator() || !item.value().isSong()) if (item.isSeparator() || !item.value().isSong())
return filter; return filter;
return Regex::search(SEItemToString(item.value()), rx); return Regex::search(SEItemToString(item.value()), rx, Config.ignore_diacritics);
} }
} }

View File

@@ -45,7 +45,7 @@ void DisplayComponent(SelectedItemsAdder::Component &menu)
bool EntryMatcher(const Regex::Regex &rx, const NC::Menu<SelectedItemsAdder::Entry>::Item &item) bool EntryMatcher(const Regex::Regex &rx, const NC::Menu<SelectedItemsAdder::Entry>::Item &item)
{ {
if (!item.isSeparator()) if (!item.isSeparator())
return Regex::search(item.value().item(), rx); return Regex::search(item.value().item(), rx, Config.ignore_diacritics);
else else
return false; return false;
} }

View File

@@ -1191,12 +1191,12 @@ bool DirEntryMatcher(const Regex::Regex &rx, const std::pair<std::string, std::s
{ {
if (dir.first == "." || dir.first == "..") if (dir.first == "." || dir.first == "..")
return filter; return filter;
return Regex::search(dir.first, rx); return Regex::search(dir.first, rx, Config.ignore_diacritics);
} }
bool SongEntryMatcher(const Regex::Regex &rx, const MPD::MutableSong &s) bool SongEntryMatcher(const Regex::Regex &rx, const MPD::MutableSong &s)
{ {
return Regex::search(SongToString(s), rx); return Regex::search(SongToString(s), rx, Config.ignore_diacritics);
} }
} }

View File

@@ -466,6 +466,7 @@ bool Configuration::read(const std::vector<std::string> &config_paths, bool igno
invalid_value(v); invalid_value(v);
}); });
p.add("ignore_leading_the", &ignore_leading_the, "no", yes_no); p.add("ignore_leading_the", &ignore_leading_the, "no", yes_no);
p.add("ignore_diacritics", &ignore_diacritics, "no", yes_no);
p.add("block_search_constraints_change_if_items_found", p.add("block_search_constraints_change_if_items_found",
&block_search_constraints_change, "yes", yes_no); &block_search_constraints_change, "yes", yes_no);
p.add("mouse_support", &mouse_support, "yes", yes_no); p.add("mouse_support", &mouse_support, "yes", yes_no);

View File

@@ -154,6 +154,7 @@ struct Configuration
bool display_bitrate; bool display_bitrate;
bool display_remaining_time; bool display_remaining_time;
bool ignore_leading_the; bool ignore_leading_the;
bool ignore_diacritics;
bool block_search_constraints_change; bool block_search_constraints_change;
bool use_console_editor; bool use_console_editor;
bool use_cyclic_scrolling; bool use_cyclic_scrolling;