Как заменить символы латинского алфавита в 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 в свой код. Фактические регулярные выражения будут выглядеть намного уродливее.

Ответ 1

Rails уже встроен для нормализации, вам просто нужно использовать это, чтобы нормализовать вашу строку, чтобы сформировать KD, а затем удалить другие символы (например, акцентные знаки) следующим образом:

>> "àáâãäå".mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.to_s
=> "aaaaaa"

Ответ 2

ActiveSupport::Inflector.transliterate (требуется Rails 2.2.1+ и Ruby 1.9 или 1.8.7)

пример:

>> ActiveSupport::Inflector.transliterate("àáâãäå").to_s => "aaaaaa"

Ответ 3

Еще лучше использовать I18n:

1.9.3-p392 :001 > require "i18n"
 => false
1.9.3-p392 :002 > I18n.transliterate("Olá Mundo!")
 => "Ola Mundo!"

Ответ 4

Я пробовал много таких подходов, но они не достигли одного или нескольких из этих требований:

  • Уважать пробелы
  • Символ уважения '-'
  • Респект (я знаю, не является обязательным для исходного вопроса, но нетрудно переместить строку в нижний регистр)

Было ли это:

# coding: utf-8
string.tr(
  "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
  "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
)

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

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

Ответ 5

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

Ответ 6

Я думаю, что вы, может быть, действительно не можете пойти по этому пути. Если вы развиваетесь на рынке, у которого есть такие письма, ваши пользователи, вероятно, подумают, что вы своего рода... пип. Потому что "å" даже не приближается к "a" в каком-либо смысле для пользователя. Возьмите другую дорогу и прочитайте о поиске по-не-ascii. Это всего лишь один из тех случаев, когда кто-то изобрел юникод и сопоставление.

Очень поздний PS:

http://www.w3.org/International/wiki/Case_folding http://www.w3.org/TR/charmod-norm/#sec-WhyNormalization

Кроме того, у меня нет никакого способа, чтобы ссылка на сопоставление переходила на страницу msdn, но я оставляю ее там. Это должно было быть http://www.unicode.org/reports/tr10/

Ответ 7

Разложите строку и удалите из нее неразрывные метки.

irb -ractive_support/all
> "àáâãäå".mb_chars.normalize(:kd).gsub(/\p{Mn}/, '')
aaaaaa

Вам также может понадобиться это, если используется в файле.rb.

# coding: utf-8

часть normalize(:kd) здесь разделяет диакритические знаки, где это возможно (например, одиночный символ "n с тильдой" разделяется на n, за которым следует объединенный диакритический символ тильды), а затем часть gsub удаляет все диакритические знаки.

Ответ 8

Предполагается, что вы используете Rails.

"anything".parameterize.underscore.humanize.downcase

Учитывая ваши требования, это, вероятно, то, что я бы сделал... Я думаю, что это аккуратно, просто и будет оставаться в курсе будущих версий Rails и Ruby.

Обновление: dgilperez указал, что parameterize принимает аргумент разделителя, поэтому "anything".parameterize(" ") (устаревший) или "anything".parameterize(separator: " ") короче и чище.

Ответ 9

Преобразуйте текст в форму нормализации D, удалите все кодовые страницы с меткой un-интервала unicode (Mn) и преобразуйте ее обратно в форму нормализации C. Это разделит все диакритики, и ваша проблема будет уменьшена до поиска без учета регистра.

См. http://www.siao2.com/2005/02/19/376617.aspx и http://www.siao2.com/2007/05/14/2629747.aspx для деталей.

Ответ 10

Ключ состоит в использовании двух столбцов в вашей базе данных: canonical_text и original_text. Используйте original_text для отображения и canonical_text для поиска. Таким образом, если пользователь ищет "Visual Cafe", она видит результат "Visual Café". Если она действительно хочет другой элемент под названием "Visual Cafe", его можно сохранить отдельно.

Чтобы получить символы canonical_text в исходном файле Ruby 1.8, сделайте следующее:

register_replacement([0x008A].pack('U'), 'S')

Ответ 11

Вероятно, вам нужно разделить Unicode ( "NFD" ). После разложения строки просто отфильтруйте что-либо не в [A-Za-z]. æ будет разлагаться на "ae", ã на "a" (приблизительно - диакритический станет отдельным символом), поэтому фильтрация оставляет разумное приближение.

Ответ 13

Для тех, кто читает это, желая удалить все символы не-ascii этот мог бы быть полезен, я успешно использовал первый пример.

Ответ 14

У меня были проблемы с получением foo.mb_chars.normalize(: kd).gsub(/[^\x00-\x7F]/n, ''). Решение downcase.to_s для работы. Я не использую Rails, и я столкнулся с некоторыми конфликтами с моими версиями activivesupport/ruby, которые я не мог получить в нижней части.

Использование драгоценного камня ruby-unf кажется хорошей заменой:

require 'unf'
foo.to_nfd.gsub(/[^\x00-\x7F]/n,'').downcase

Насколько я могу сказать, это делает то же самое, что и .mb_chars.normalize(: kd). Это верно? Спасибо!

Ответ 15

Если вы используете 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. Вот обсуждение этих двух подходов. Также обратите внимание на то, что использование функции PosgreSQL lower() не рекомендуется.

Это сэкономит вам некоторое пространство БД, поскольку вам больше не потребуется поле cannonical_name и, возможно, сделает вашу модель проще, за счет некоторой дополнительной обработки в каждом запросе, в размере, зависящем от того, используете ли вы iLIKE или citext, и ваш набор данных.

Если вы используете MySQL, возможно, вы можете использовать это простое решение, но я не проверял его.

Ответ 16

lol.. я просто попробовал это.. и он работает.. я все еще не совсем уверен, почему.. но когда я использую эти 4 строки кода:

  • str = str.gsub(/[^ a-zA-Z0-9]/, "")
  • str = str.gsub(/[] +/, "")
  • str = str.gsub(//, "-" )
  • str = str.downcase

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