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.
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"
ActiveSupport::Inflector.transliterate
(nécessite Rails 2.2.1+ et Ruby 1.9 ou 1.8.7)
exemple:
>> ActiveSupport::Inflector.transliterate("àáâãäå").to_s
=> "aaaaaa"
Encore mieux, utilisez I18n:
1.9.3-p392 :001 > require "i18n"
=> false
1.9.3-p392 :002 > I18n.transliterate("Olá Mundo!")
=> "Ola Mundo!"
J'ai essayé beaucoup de ces approches, mais elles ne remplissaient pas une ou plusieurs de ces exigences:
A été ceci:
# coding: utf-8
string.tr(
"ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšſŢţŤťŦŧÙÚÛÜùúûüŨũŪūŬŭŮůŰűŲųŴŵÝýÿŶŷŸŹźŻżŽž",
"AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssTtTtTtUUUUuuuuUuUuUuUuUuUuWwYyyYyYZzZzZz"
)
Vous devez modifier un peu la liste des caractères pour respecter le caractère 'ñ', mais c'est un travail facile.
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"
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/
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.
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.
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 .
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')
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.
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.
iconv:
http://groups.google.com/group/Ruby-talk-google/browse_frm/thread/8064dcac15d688ce ?
=============
un module Perl que je ne comprends pas:
http://www.ahinea.com/fr/tech/accented-translate.html
============
force brute (il y a beaucoup de bestioles htose !:
http://projects.jkraemer.net/acts_as_ferret/wiki#UTF-8support
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!
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.