Как заменить ударные латинские буквы в 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"
)

- http://blog.slashpoundbang.com/post/12938588984/remove-all-accents-and-diacritics-from-string-in-ruby

Вы должны немного изменить список символов, чтобы он соответствовал символу "-", но это простая работа.

Мой ответ: метод 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

============

грубая сила (там много тварей!

http://projects.jkraemer.net/acts_as_ferret/wiki

http://snippets.dzone.com/posts/show/2384

Если вы используете 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

он автоматически удаляет любой акцент из имен файлов.. который я пытался удалить (ударение из имен файлов и переименовывает их, чем) надеюсь, что это помогло:)

Другие вопросы по тегам