web-dev-qa-db-fra.com

Comment remplacer des caractères latins accentués dans Ruby?

J'ai un modèle ActiveRecord, Foo, qui a un champ name. J'aimerais que les utilisateurs puissent rechercher par nom, mais que la recherche ignore le cas et les accents. Ainsi, je stocke également un champ canonical_name dans lequel rechercher:

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

Je dois remplir le "quelque chose ici" pour remplacer les caractères accentués. Y a-t-il quelque chose de mieux que

x.downcase.gsub(/[àáâãäå]/,'a').gsub(/æ/,'ae').gsub(/ç/, 'c').gsub(/[èéêë]/,'e')....

Et, d'ailleurs, comme je ne suis pas sur Ruby 1.9, je ne peux pas mettre ces littéraux Unicode dans mon code. Les expressions régulières réelles seront beaucoup plus laides.

66
James A. Rosen

Rails a déjà été intégré à la normalisation, il vous suffit de l'utiliser pour normaliser votre chaîne afin de former un KD, puis supprimer les autres caractères (c.-à-d. Les marques d'accent), comme ceci:

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

ActiveSupport::Inflector.transliterate (nécessite Rails 2.2.1+ et Ruby 1.9 ou 1.8.7)

exemple: 

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

85
Mark Wilden

Encore mieux, utilisez I18n:

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

J'ai essayé beaucoup de ces approches, mais elles ne remplissaient pas une ou plusieurs de ces exigences:

  • Espaces de respect
  • Caractère de respect 'ñ'
  • Respect case (je sais que ce n'est pas obligatoire pour la question d'origine mais qu'il n'est pas difficile de déplacer une chaîne dans lowcase)

A été ceci:

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

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

Vous devez modifier un peu la liste des caractères pour respecter le caractère 'ñ', mais c'est un travail facile.

19
fguillen

Ma réponse: the String # parameterize method:

"Le cœur de la crémiére".parameterize
=> "le-coeur-de-la-cremiere"

Pour les programmes non-Rails:

Installez actifsupport: gem install activesupport puis:

require 'active_support/inflector'
"a&]'s--3\014\xC2àáâã3D".parameterize
# => "a-s-3-3d"
10
Dorian

Je pense que vous ne savez peut-être pas vraiment quoi faire dans cette voie. Si vous vous développez pour un marché qui contient ce type de lettres, vos utilisateurs penseront probablement que vous êtes une sorte de ...pip. Parce que 'å' n'est même pas proche de 'a' dans un sens quelconque pour un utilisateur .. Prenez un chemin différent et lisez en détail sur la recherche d'une manière non ascii. Ceci est juste un de ces cas où quelqu'un a inventé unicode et collation .

Un PS très tardif:

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

En plus de cela, je n'ai aucune idée du fait que le lien de classement va à une page msdn mais je le laisse là. Cela aurait dû être http://www.unicode.org/reports/tr10/

7
Jonke

Décomposez la chaîne et supprimez marques non d'espacement de celle-ci.

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

Vous pouvez également en avoir besoin si vous l'utilisez dans un fichier .rb.

# coding: utf-8

la partie normalize(:kd) divise les diacritiques dans la mesure du possible (par exemple: le caractère unique "n avec tilda" est divisé en un n suivi d'un caractère tilda diacritique combinant), et la partie gsub supprime tous les caractères diacritiques.

5
Cheng

Cela suppose que vous utilisez Rails.

"anything".parameterize.underscore.humanize.downcase

Compte tenu de vos besoins, c'est probablement ce que je ferais ... Je pense que c'est soigné, simple et qu'il restera à jour dans les futures versions de Rails et Ruby.

Mise à jour: dgilperez a souligné que parameterize prend un argument de séparateur, donc "anything".parameterize(" ") (obsolète) ou "anything".parameterize(separator: " ") est plus court et plus propre.

4
Sudhir Jonathan

Convertissez le texte en formulaire de normalisation D, supprimez tous les points de code avec la marque non d'espacement de catégorie unicode (Mn), puis reconvertissez-le en formulaire de normalisation C. Cela supprimera tous les signes diacritiques et réduira votre problème à une recherche insensible à la casse.

Voir http://www.siao2.com/2005/02/19/376617.aspx et http://www.siao2.com/2007/05/14/2629747.aspx pour plus de détails .

3
CesarB

La clé consiste à utiliser deux colonnes dans votre base de données: canonical_text et original_text. Utilisez original_text pour l'affichage et canonical_text pour les recherches. Ainsi, si un utilisateur recherche "Visual Cafe", il voit le résultat "Visual Café". Si elle vraiment veut un autre élément appelé "Visual Cafe", il peut être enregistré séparément.

Pour obtenir les caractères canonical_text dans un fichier source Ruby 1.8, procédez comme suit:

register_replacement([0x008A].pack('U'), 'S')
3
James A. Rosen

Vous voulez probablement une décomposition Unicode ("NFD"). Après avoir décomposé la chaîne, il suffit de filtrer tout ce qui n’est pas dans [A-Za-z]. æ va se décomposer en "ae", ã en "a ~" (environ - le diacritique deviendra un caractère séparé) afin que le filtrage laisse une approximation raisonnable.

2
MSalters

Pour tous ceux qui liront ceci, ils voudront supprimer tous les caractères non-ASCII this peuvent être utiles, j’ai utilisé le premier exemple avec succès.

1
Kris
1
Gene T

J'ai eu des problèmes pour obtenir la solution foo.mb_chars.normalize (: kd) .gsub (/ [^\x00-\x7F]/n, ''). Downcase.to_s. Je n'utilise pas Rails et il y avait des conflits avec mes versions de Active Support/Ruby que je ne pouvais pas aller au fond des choses.

Utiliser la gemme Ruby-unf semble être un bon substitut:

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

Autant que je sache, cela fait la même chose que .mb_chars.normalize (: kd). Est-ce correct? Merci!

0
eoghan.ocarragain

Si vous utilisez PostgreSQL => 9.4 en tant qu'adaptateur de base de données, vous pourriez peut-être ajouter à une migration que c'est l'extension "unaccent" qui, je pense, fait ce que vous voulez, comme ceci:

def self.up
   enable_extension "unaccent" # No falla si ya existe
end

Pour tester, dans la console:

2.3.1 :045 > ActiveRecord::Base.connection.execute("SELECT unaccent('unaccent', 'àáâãäåÁÄ')").first
 => {"unaccent"=>"aaaaaaAA"}

Notez qu'il est sensible à la casse jusqu'à présent.

Ensuite, utilisez-le peut-être dans un scope, comme:

scope :with_canonical_name, -> (name) {
   where("unaccent(foos.name) iLIKE unaccent('#{name}')")
}

L'opérateur iLIKE rend la recherche insensible à la casse. Il existe une autre approche, utilisant citext type de données. Ici est une discussion sur ces deux approches. Notez également que l'utilisation de la fonction lower () de PosgreSQL n'est pas recommandée .

Cela vous permettra d'économiser de l'espace sur la base de données, car vous n'aurez plus besoin du champ cannonical_name, et simplifiera peut-être votre modèle, au prix d'un traitement supplémentaire dans chaque requête, d'un montant variant selon que vous utilisez iLIKE ou citext votre jeu de données.

Si vous utilisez MySQL, vous pouvez peut-être utiliser cette solution simple , mais je ne l’ai pas testée.

0
user2553863