Как заменить ударные латинские буквы в Ruby?
У меня есть ActiveRecord
модель, Foo
, который имеет name
поле. Я бы хотел, чтобы пользователи могли выполнять поиск по имени, но я бы хотел, чтобы поиск игнорировал регистр и любые акценты. Таким образом, я также храню canonical_name
поле для поиска:
class Foo
validates_presence_of :name
before_validate :set_canonical_name
private
def set_canonical_name
self.canonical_name ||= canonicalize(self.name) if self.name
end
def canonicalize(x)
x.downcase. # something here
end
end
Мне нужно заполнить "что-то здесь", чтобы заменить акцентированные символы. Есть ли что-нибудь лучше, чем
x.downcase.gsub(/[àáâãäå]/,'a').gsub(/æ/,'ae').gsub(/ç/, 'c').gsub(/[èéêë]/,'e')....
И, в этом отношении, так как я не на Ruby 1.9, я не могу поместить эти литералы Unicode в мой код. Фактические регулярные выражения будут выглядеть намного ужаснее.
16 ответов
В Rails уже есть встроенная функция для нормализации, вы просто должны использовать это для нормализации вашей строки, чтобы сформировать KD, а затем удалить другие символы (например, знаки ударения) следующим образом:
>> "àáâãäå".mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.to_s
=> "aaaaaa"
ActiveSupport::Inflector.transliterate
(требуется Rails 2.2.1+ и Ruby 1.9 или 1.8.7)
пример:
>> ActiveSupport::Inflector.transliterate("àáâãäå").to_s
=> "aaaaaa"
Еще лучше использовать I18n:
1.9.3-p392 :001 > require "i18n"
=> false
1.9.3-p392 :002 > I18n.transliterate("Olá Mundo!")
=> "Ola Mundo!"
Я пробовал многие из этих подходов, но они не достигли одного или нескольких из этих требований:
- Респект Пространства
- Уважение к персонажу
- Уважать регистр (я знаю, что это не является обязательным требованием для первоначального вопроса, но нетрудно переместить строку в нижний регистр)
Было это:
# coding: utf-8
string.tr(
"ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
"AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
)
Вы должны немного изменить список символов, чтобы он соответствовал символу "-", но это простая работа.
Мой ответ: метод String# параметризации:
"Le cœur de la crémiére".parameterize
=> "le-coeur-de-la-cremiere"
Для не-Rails программ:
Установить activesupport: gem install activesupport
затем:
require 'active_support/inflector'
"a&]'s--3\014\xC2àáâã3D".parameterize
# => "a-s-3-3d"
Разложите строку и удалите из нее неразрывные метки.
irb -ractive_support/all
> "àáâãäå".mb_chars.normalize(:kd).gsub(/\p{Mn}/, '')
aaaaaa
Вам также может понадобиться это, если используется в файле.rb.
# coding: utf-8
normalize(:kd)
часть здесь разделяет диакритические знаки, где это возможно (например, одиночный символ "n с тильдой" разделяется на n, за которым следует объединяющий диакритический символ тильда), и gsub
Затем часть удаляет все диакритические знаки.
Я думаю, что вы, возможно, не совсем то, что идти по этому пути. Если вы разрабатываете для рынка с такими буквами, ваши пользователи, вероятно, подумают, что вы что-то вроде...пипс. Потому что "å" даже не близко к "a" в каком-либо смысле для пользователя. Выбери другую дорогу и прочитай о поиске не ascii способом. Это только один из тех случаев, когда кто-то изобрел юникод и сопоставление.
Очень поздно PS:
http://www.w3.org/International/wiki/Case_folding http://www.w3.org/TR/charmod-norm/
Кроме того, у меня нет никакого идеального пути, чтобы ссылка на сопоставление пошла на страницу msdn, но я оставляю ее там. Это должен был быть http://www.unicode.org/reports/tr10/
Это предполагает, что вы используете Rails.
"anything".parameterize.underscore.humanize.downcase
Учитывая ваши требования, это, вероятно, то, что я бы сделал... Я думаю, что это аккуратно, просто и будет актуально в будущих версиях Rails и Ruby.
Обновление: dgilperez указал, что parameterize
принимает аргумент-разделитель, так "anything".parameterize(" ")
(устарело) или "anything".parameterize(separator: " ")
короче и чище.
Преобразуйте текст в форму нормализации D, удалите все кодовые точки с меткой без пробелов категории Unicode (Mn) и верните ее обратно в форму нормализации C. Это исключит все диакритические знаки, и ваша проблема будет сведена к поиску без учета регистра.
См. http://www.siao2.com/2005/02/19/376617.aspx и http://www.siao2.com/2007/05/14/2629747.aspx для получения подробной информации.
Ключ должен использовать два столбца в вашей базе данных: canonical_text
а также original_text
, использование original_text
для отображения и canonical_text
для поисков. Таким образом, если пользователь ищет "Визуальное кафе", он видит результат "Визуальное кафе". Если она действительно хочет другой элемент под названием "Визуальное кафе", его можно сохранить отдельно.
Чтобы получить символы canonical_text в исходном файле Ruby 1.8, сделайте что-то вроде этого:
register_replacement([0x008A].pack('U'), 'S')
Вы, вероятно, хотите декомпозицию Unicode ("NFD"). После разложения строки просто отфильтруйте все, что не в [A-Za-z]. æ разложится на "ae", ã на "a~" (примерно - диакритический знак станет отдельным символом), поэтому фильтрация оставляет разумное приближение.
Для тех, кто читает это и хочет убрать все не-ascii символы, это может быть полезно, я успешно использовал первый пример.
Iconv:
http://groups.google.com/group/ruby-talk-google/browse_frm/thread/8064dcac15d688ce?
=============
модуль Perl, который я не могу понять:
http://www.ahinea.com/en/tech/accented-translate.html
============
грубая сила (там много тварей!
Если вы используете PostgreSQL => 9.4 в качестве адаптера БД, возможно, вы могли бы добавить в миграцию расширение "unaccent", которое, я думаю, выполняет то, что вы хотите, например:
def self.up
enable_extension "unaccent" # No falla si ya existe
end
Для тестирования в консоли:
2.3.1 :045 > ActiveRecord::Base.connection.execute("SELECT unaccent('unaccent', 'àáâãäåÁÄ')").first
=> {"unaccent"=>"aaaaaaAA"}
Обратите внимание, что до сих пор чувствительны к регистру.
Затем, возможно, используйте его в области видимости, например:
scope :with_canonical_name, -> (name) {
where("unaccent(foos.name) iLIKE unaccent('#{name}')")
}
Оператор iLIKE делает регистр поиска нечувствительным. Существует другой подход, использующий тип данных citext. Вот обсуждение этих двух подходов. Также обратите внимание, что не рекомендуется использовать функцию Lower () в PosgreSQL.
Это сэкономит вам некоторое пространство БД, поскольку вам больше не потребуется поле cannonical_name и, возможно, сделает вашу модель проще, за счет некоторой дополнительной обработки в каждом запросе, в размере, зависящем от того, используете ли вы iLIKE или citext, и ваш набор данных.
Если вы используете MySQL, возможно, вы можете использовать это простое решение, но я не проверял его.
У меня были проблемы с получением решения foo.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,''). Downcase.to_s. Я не использую Rails, и у меня были некоторые конфликты с моими версиями activesupport/ruby, до которых я не смог добраться до сути.
Использование камня ruby-unf кажется хорошей заменой:
require 'unf'
foo.to_nfd.gsub(/[^\x00-\x7F]/n,'').downcase
Насколько я могу судить, это делает то же самое, что и.mb_chars.normalize (: kd). Это правильно? Спасибо!
Лол.. я просто попробовал это.. и он работает.. я все еще не совсем уверен, почему.. но когда я использую это 4 строки кода:
- str = str.gsub (/ [^ a-zA-Z0-9] /, "")
- str = str.gsub (/ [] + /, "")
- str = str.gsub (/ /, "-")
- str = str.downcase
он автоматически удаляет любой акцент из имен файлов.. который я пытался удалить (ударение из имен файлов и переименовывает их, чем) надеюсь, что это помогло:)