Wie ersetze ich lateinische Zeichen mit Akzent in Ruby?

73

Ich habe ein ActiveRecordModell Foo, das ein nameFeld hat. Ich möchte, dass Benutzer nach Namen suchen können, aber ich möchte, dass bei der Suche Groß- und Kleinschreibung und Akzente ignoriert werden. Daher speichere ich auch ein canonical_nameFeld, nach dem gesucht werden soll:

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

Ich muss das "Etwas hier" ausfüllen, um die akzentuierten Zeichen zu ersetzen. Gibt es etwas besseres als

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

Und da ich nicht auf Ruby 1.9 bin, kann ich diese Unicode-Literale nicht in meinen Code einfügen. Die tatsächlichen regulären Ausdrücke sehen viel hässlicher aus.

James A. Rosen
quelle
2
sogar in 1.8 können Sie "ruby-Ku"
Keltia
Dieses Problem ist längst gelöst und es gibt viele gute Kommentare unten. Wenn ich es jetzt noch einmal lese, möchte ich eines klarstellen: Die Idee war, eine Version des Textes zu erstellen, die nur mit ASCII-Zeichen durchsucht werden konnte, um die Daten nicht tatsächlich zu erzwingen. Beachten Sie, dass es zwei Datenbankeigenschaften gibt: nameund canonical_name. Ich befürworte nicht, die tatsächlichen Daten zu verwerfen, sondern lediglich eine Möglichkeit zu schaffen, sie ohne diakritische Zeichen zu durchsuchen, die Benutzer aller Sprachen häufig weglassen.
James A. Rosen
1
Eigentlich ist jede einzelne davon die falsche Antwort. Sie müssen den Unicode-Kollatierungsalgorithmus mit einer Vergleichsstärke verwenden, die nur auf Stufe 1 eingestellt ist. Alles andere ist vermasselt.
Tchrist
8
@tchrist, also bist du zur Diskussion gekommen, um zu sagen, "diese Leute liegen falsch", hast aber nicht mehr als die geringsten Antworten angeboten? o_O Bitte beantworte die Frage wirklich, damit ich dich dafür abstimmen kann, dass du widerlich bist.
JCollum
@tchrist "falsch" kann von individuellen Anforderungen abhängen. Richtig, falsch zu sein kann zurückkommen, um jemanden zu verfolgen, der die Konsequenzen nicht kennt (und folglich nicht die Anforderung hinzugefügt hat, die er hinzugefügt hätte, wenn er es besser gewusst hätte). Aber bis ihnen die genannten Konsequenzen mitgeteilt werden, werden sie dem Vorschlag nicht folgen.
Kelvin

Antworten:

62

Rails verfügt bereits über eine integrierte Funktion zum Normalisieren. Sie müssen diese nur verwenden, um Ihre Zeichenfolge zu normalisieren, um KD zu bilden, und dann die anderen Zeichen (dh Akzentzeichen) wie folgt entfernen:

>> "àáâãäå".mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').downcase.to_s
=> "aaaaaa"
nicht vorhanden
quelle
1
+1 für die Form KD, die auch Ligaturen wie 'ffi' in 'ffi' umwandelt.
Christian - Stellen Sie Monica C
4
Ich versuche dies in einem anderen Skript außerhalb einer Rails-App zu verwenden. Ich dachte, es wäre drin activesupport, aber nachdem ich es verlangt habe, bekomme ich immer noch ein NoMethodErrorfür normalize. Wissen Sie, was ich benötigen muss?
Helder S Ribeiro
4
Es befindet sich in aktiver Unterstützung, aber Sie müssen es folgendermaßen ausführen: ActiveSupport :: Multibyte :: Chars.new ("àáâãäå"). Mb_chars.normalize (: kd) .gsub (/ [^ \ x00- \ x7F] / n, ''). downcase.to_s
vorhanden
7
Das funktioniert super, aber ich musste es mb_charswie Christian machen. foo.mb_chars.normalize(:kd).gsub(/[^\x00-\x7F]/n,'').to_s.split
Sam Soffes
49
Zumindest in Rails3 funktioniert String # parameterize ... also "öüâ" .parameterize == "oua"
foz
93

ActiveSupport::Inflector.transliterate (erfordert Rails 2.2.1+ und Ruby 1.9 oder 1.8.7)

Beispiel:

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

Mark Wilden
quelle
Sie müssen require 'active_support/inflector'in Nicht-Rails-Projekt (siehe meine Antwort unten)
Dorian
Du bist der echte MVP
Cesartalves
41

Besser noch ist es, I18n zu verwenden:

1.9.3-p392 :001 > require "i18n"
 => false
1.9.3-p392 :002 > I18n.transliterate("Olá Mundo!")
 => "Ola Mundo!"
Diego Moreira
quelle
1
in normalem Ruby (keine Schienen) bekomme ich: LoadError: Kann eine solche Datei nicht laden - i18n Schienenbibliothek? Wie auch immer, die Rails-Methode ActiveSupport :: Inflector.transliterate ruft tatsächlich die I18n unter der Decke auf (nach einer Normalisierung, um sicherzustellen, dass alle diakritischen Markierungen entfernt werden können)
Rogerdpack
1
Für cannot load such file -- i18nnur sudo gem install i18n.
Camille Goudeseune
Ich habe einen Fehler wie :en is not a valid locale (I18n::InvalidLocale)bis ich hinzugefügt habe I18n.available_locales = [:en]. Dies ersetzt auch Nicht-ASCII-Zeichen, die keine lateinischen Buchstaben sind, durch diakritische Zeichen mit Fragezeichen, sodass beispielsweise ruby -ri18n -ne'I18n.available_locales=[:en];puts I18n.transliterate$_'<<<Дあ☆gedruckt wird ???.
Nisetama
17

Ich habe viele dieser Ansätze ausprobiert, aber sie haben eine oder mehrere dieser Anforderungen nicht erfüllt:

  • Räume respektieren
  • Respektiere 'ñ' Charakter
  • Respektieren Sie den Fall (ich weiß, dass dies keine Voraussetzung für die ursprüngliche Frage ist, aber es ist nicht schwierig, eine Zeichenfolge in Kleinbuchstaben zu verschieben ).

War das:

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

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

Sie müssen die Zeichenliste ein wenig ändern, um das Zeichen 'ñ' zu respektieren, aber es ist eine leichte Aufgabe.

fguillen
quelle
Können Sie näher erläutern, was Sie damit meinen, dass Sie die Charakterliste ändern müssen, um den Charakter zu respektieren ñ? Es scheint mir, dass es bereits in der Liste ist und mit ausgerichtet ist n.
user664833
Um den ñCharakter zu respektieren , möchte ich ihn NICHT in einen nCharakter verwandeln, sondern behalten.
Fguillen
Aha. Kannst du sagen, warum dieser Charakter etwas Besonderes ist (ich meine, warum er aus Respekt herausgegriffen wird)?
user664833
1
Entschuldigung, aber ich verstehe immer noch nicht, warum Sie sich entschieden haben, ñin Ihrer Liste mit Anforderungen mit Aufzählungszeichen herauszugreifen. ñist der "lateinische Kleinbuchstabe n mit Tilde" und befindet sich im erweiterten ASCII-Satz, zusammen mit vielen anderen in Ihrer Liste - siehe ascii-code.com -, während Ihre Liste eine Reihe von Zeichen enthält, die dies nicht sind im erweiterten ASCII-Satz, einschließlich Ąund Ħ. Ich bin immer noch verwirrt darüber, warum Sie herausgegriffen haben ñ.
user664833
1
ActiveSupport::Inflector.transliteratescheint Ihre Anforderungen zu erfüllen, mit Ausnahme der "Retention ñ" und auch dieser Weg ist reiner Rubin, was schön ist. Leider können Sie mit Unicode seltsame Dinge tun, z. B. einen Umlaut über praktisch alle vorhergehenden Zeichen hinzufügen. Daher ist es schwierig, diesen Ansatz so umfassend zu gestalten, dass alle Situationen erfüllt werden: |
Rogerdpack
12

Meine Antwort: die String # -Parameterisierungsmethode :

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

Für Nicht-Rails-Programme:

Installiere activesupport: gem install activesupportdann:

require 'active_support/inflector'
"a&]'s--3\014\xC2àáâã3D".parameterize
# => "a-s-3-3d"
Dorian
quelle
Es gibt einen großen Unterschied zu. parametrisieren versus ActiveSupport :: Inflector.transliterate Eingabe: "🕳 Fallen Sie nicht in diese Programmierfalle" .parameterize ergibt: "Nicht in diese Programmierfalle fallen" ActiveSupport :: Inflector.transliterate gibt: "? Fallen Sie nicht in diese Programmierfalle "Das ist ein großer Unterschied.
Fuzzygroup
@fuzzygroup Die Verwendung der Code-Formatierung von Markdown (z. B. `method`) hilft beim Lesen des Teils des Kommentars. Und um Ihre Frage zu beantworten, "Le cœur de la crémiére".parameterizeist das beste UTF-8 zu ASCII für URLs, es ist super nett und süß
Dorian
7

Ich denke, dass Sie vielleicht nicht wirklich wissen, was Sie auf diesem Weg tun sollen. Wenn Sie sich für einen Markt mit solchen Buchstaben entwickeln, werden Ihre Benutzer wahrscheinlich denken, dass Sie eine Art ... Pip sind . Weil 'å' für einen Benutzer nicht einmal in der Nähe von 'a' liegt. Nehmen Sie einen anderen Weg und informieren Sie sich über die Suche auf nicht-ASCII-Weise. Dies ist nur einer dieser Fälle, in denen jemand Unicode und Kollation erfunden hat .

Eine sehr späte PS :

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

Außerdem habe ich keine Ahnung, wie der Link zur Sortierung zu einer MSDN-Seite führt, aber ich lasse ihn dort. Es sollte http://www.unicode.org/reports/tr10/ gewesen sein.

Jonke
quelle
Ich bin alle für die Datenbanksortierung, aber jemand könnte ein Jahr nach meiner Abreise die Datenbank wechseln. Ich würde es vorziehen, defensiv zu sein und es zumindest im Code und möglicherweise auch in der DB zu tun. Um die Benutzer zu zwingen, einzugeben, was sie bedeuten: Wie viele englische Benutzer geben einen Lebenslauf ein? Oder "visuelles Café"?
James A. Rosen
Im Streifen hat das Plakat die Buchstaben å und ä. Wenn Sie diese entfernen, ist die Bedeutung des Wortes, in dem sie sich befinden, bedeutungslos. Sie können diese nicht entfernen und verwenden, was übrig bleibt. Wenn Sie wirklich für einen europäischen Markt arbeiten, lernen Sie besser, mit etwas zu suchen, anstatt die Benutzerdaten zu verwerfen.
Jonke
In der slowakischen Sprache zum Beispiel ist á, ä sehr nahe an a. Und so sind alle Zeichen mit Akzent zu denen ohne Akzent. Viele Leute benutzen diese überhaupt nicht in IM usw.
Vojto
@Vojto: In den meisten modernen europäischen Sprachen sind Akzentzeichen weit entfernt von den Versionen ohne Akzent. Tatsächlich sind das Symbole für sehr unterschiedliche Klänge. Das deutsche Öl zum Beispiel ( en.bab.la/dictionary/german-english/oel ). Oder die schwedischen Wörter ål (eal) und al (ein Baum).
Jonke
Cool, ich wollte nur erwähnen, dass das nicht unbedingt für alle europäischen Sprachen gilt. Ich habe Slowakisch erwähnt, aber es ist das gleiche auch für Tschechisch, Polnisch, Kroatisch und so ziemlich alle slawischen Sprachen. Und es ist sehr wichtig, dass Suchmaschinen usw. die Suche nach Zeichen ohne Akzent unterstützen - da die Leute in den meisten Fällen einfach zu faul sind, um Akzente zu setzen.
Vojto
7

Zerlegen Sie die Zeichenfolge und entfernen Sie nicht voneinander entfernte Markierungen .

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

Möglicherweise benötigen Sie dies auch, wenn Sie es in einer .rb-Datei verwenden.

# coding: utf-8

Der normalize(:kd)Teil hier teilt diakritische Zeichen nach Möglichkeit auf (z. B. das einzelne Zeichen "n mit Tilda" wird in ein n aufgeteilt, gefolgt von einem kombinierten diakritischen Tilda-Zeichen), und der gsubTeil entfernt dann alle diakritischen Zeichen.

Cheng
quelle
Siehe auch die Antwort von nicht vorhanden hier, die dies im Wesentlichen tut, jedoch mit einem 1.8.x-kompatiblen regulären Ausdruck.
Rogerdpack
2
Dies sollte viel höher sein. Andere Lösungen entfernen andere Zeichensätze (z . B. I18n.transliterate('日本語') #=> "???") und vollständig '日本語'.parameterize #=> "". Diese Antwort entspricht am ehesten meinen Anforderungen, nämlich in der Lage zu sein, verschiedene Datensätze zu Titeln / Autoren ungefähr abzugleichen. '日本語 àáâãäå'.unicode_normalize(:nfkd).gsub(/\p{Mn}/, '') #=> "日本語 aaaaaa"
Bo Jeanes
4

Dies setzt voraus, dass Sie Rails verwenden.

"anything".parameterize.underscore.humanize.downcase

Angesichts Ihrer Anforderungen würde ich dies wahrscheinlich tun ... Ich denke, es ist ordentlich, einfach und wird in zukünftigen Versionen von Rails und Ruby auf dem neuesten Stand bleiben.

Update: dgilperez wies darauf hin, dass parameterizeein Trennzeichen Argument, also "anything".parameterize(" ")(veraltet) oder "anything".parameterize(separator: " ")kürzer und sauberer ist.

Sudhir Jonathan
quelle
3
Wäre nicht "anything".parameterize(" ")kürzer?
Dgilperez
Oh, danke, ich wusste nicht, dass Parametrisierung Argumente benötigt.
Sudhir Jonathan
.parameterize(" ")ist kürzer, aber es fällt alles runter. Und ich habe keine Möglichkeit gefunden, das preserve_caseArgument an den Ausdruck anzuhängen . I18n.transliterateist am effizientesten und effektivsten.
Jerome
3

Konvertieren Sie den Text in das Normalisierungsformular D, entfernen Sie alle Codepunkte mit der Unicode-Kategorie ohne Abstand (Mn) und konvertieren Sie ihn zurück in das Normalisierungsformular C. Dadurch werden alle diakritischen Zeichen entfernt, und Ihr Problem wird auf eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung reduziert.

Weitere Informationen finden Sie unter http://www.siao2.com/2005/02/19/376617.aspx und http://www.siao2.com/2007/05/14/2629747.aspx .

CesarB
quelle
Verwandte Antwort: stackoverflow.com/questions/285228/…
CesarB
Alle diese Antworten, die Normalisierungsformen beinhalten, sind alle falsch. Sie benötigen einen UCA-Level-1-Vergleich, möglicherweise mit Gebietsschemaanpassung.
Tchrist
Siehe auch Cheng's Antwort hier
Rogerdpack
3

Der Schlüssel besteht darin, zwei Spalten in Ihrer Datenbank zu verwenden: canonical_textund original_text. Zur original_textAnzeige und canonical_textSuche verwenden. Auf diese Weise sieht ein Benutzer, wenn er nach "Visual Cafe" sucht, das Ergebnis "Visual Café". Wenn sie wirklich einen anderen Gegenstand namens "Visual Cafe" möchte, kann dieser separat gespeichert werden.

Gehen Sie folgendermaßen vor, um die Zeichen canonical_text in einer Ruby 1.8-Quelldatei abzurufen:

register_replacement([0x008A].pack('U'), 'S')
James A. Rosen
quelle
Vielleicht eine Nisse, aber der Name 'canonical_text' würde mich ein bisschen umwerfen, da das, was wir tun, verlustbehaftet ist. Ich würde einen Namen erwarten, der eher 'compatible_text' oder 'decomposed_text' ähnelt (obwohl ich das gleiche Argument auch gegen diese sehen kann). Vielleicht nur 'search_text'?
Christian - Stellen Sie Monica C
Was ist hier register_replacement?
Rogerdpack
2

Sie möchten wahrscheinlich eine Unicode-Zerlegung ("NFD"). Filtern Sie nach dem Zerlegen des Strings einfach alles heraus, was nicht in [A-Za-z] enthalten ist. æ zerfällt in "ae", ã in "a ~" (ungefähr - das diakritische Zeichen wird zu einem separaten Zeichen), sodass die Filterung eine vernünftige Annäherung hinterlässt.

MSalters
quelle
1
Alle diese Antworten, die Normalisierungsformen beinhalten, sind alle falsch. Sie benötigen einen UCA-Level-1-Vergleich, möglicherweise mit Gebietsschemaanpassung.
Tchrist
1
@tchrist: Wenn Sie eine alternative Antwort geben möchten, fühlen Sie sich frei. Wenn Sie darauf hinweisen möchten, warum meine Antwort nicht funktioniert, können Sie einen Kommentar verwenden, aber dann zumindest darauf hinweisen, warum es nicht funktioniert. (Hinweis: Lesen Sie den Titel der Frage zuerst, UCA - Vergleich ist nicht ersetzen akzentuierte Zeichen).
MSalters
Siehe auch Cheng's Antwort für ein Beispiel
Rogerdpack
1

Für alle , die dies lesen , alle nicht-ASCII - Zeichen abzustreifen will dies nützlich sein könnte, habe ich das erste Beispiel erfolgreich.

Kris
quelle
1
Könnte
0

Ich hatte Probleme, die Lösung foo.mb_chars.normalize (: kd) .gsub (/ [^ \ x00- \ x7F] / n, ''). Downcase.to_s zum Laufen zu bringen. Ich benutze keine Rails und es gab einen Konflikt mit meinen aktiven Support- / Ruby-Versionen, dem ich nicht auf den Grund gehen konnte.

Die Verwendung des rubinroten Edelsteins scheint ein guter Ersatz zu sein:

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

Soweit ich das beurteilen kann, funktioniert dies genauso wie .mb_chars.normalize (: kd). Ist das richtig? Vielen Dank!

eoghan.ocarragain
quelle
0

Wenn Sie PostgreSQL => 9.4 als DB-Adapter verwenden, können Sie bei einer Migration möglicherweise die Erweiterung "inaccent" hinzufügen , die meiner Meinung nach das tut, was Sie wollen, wie folgt:

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

Zum Testen in der Konsole:

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

Beachten Sie, dass bisher zwischen Groß- und Kleinschreibung unterschieden wird.

Verwenden Sie es dann möglicherweise in einem Bereich wie:

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

Der iLIKE-Operator macht die Suche unabhängig von Groß- und Kleinschreibung. Es gibt einen anderen Ansatz, bei dem der Datentyp citext verwendet wird. Hier ist eine Diskussion über diese beiden Ansätze. Beachten Sie auch, dass die Verwendung der Funktion lower () von PosgreSQL nicht empfohlen wird .

Dies spart Ihnen etwas DB-Speicherplatz, da Sie das Feld cannonical_name nicht mehr benötigen, und vereinfacht möglicherweise Ihr Modell auf Kosten einer zusätzlichen Verarbeitung in jeder Abfrage in einer Menge, die davon abhängt, ob Sie iLIKE oder citext verwenden, und Ihr Datensatz.

Wenn Sie MySQL verwenden , können Sie vielleicht diese einfache Lösung verwenden , aber ich habe sie nicht getestet.

user2553863
quelle
-3

lol .. ich habe es gerade versucht .. und es funktioniert .. ich bin mir immer noch nicht ganz sicher warum .. aber wenn ich diese 4 Codezeilen benutze:

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

Es entfernt automatisch jeden Akzent aus Dateinamen. Ich habe versucht zu entfernen (Akzent aus Dateinamen und umbenennen als). Ich hoffe, es hat geholfen :)


quelle
2
Außerdem werden alle Zeichen entfernt, die nicht alphanumerisch sind. Welches ist wahrscheinlich nicht das richtige Verhalten, auch für einen Dateinamen.
Chuck
Dies ist nicht das erwartete Verhalten.
Mariano Cavallo
Leider werden alle Zeichen mit Akzent entfernt und nicht durch ihre nicht akzentuierten Zeichen ersetzt: |
Rogerdpack