Sichere Zeichensatzkonvertierungen mit I18n.transliterate

Viele Zeichensätze sind untereinander inkompatibel, weil nicht jeder Zeichensatz den gleichen Zeichenumfang besitzt. Beim Konvertieren von Zeichensatz X zum Zeichensatz Y kann es also passieren, dass Zeichen im Zielzeichensatz nicht vorhanden sind und deswegen nicht korrekt dargestellt werden.

In diesem Artikel zeige ich, wie man das Problem mittels Ruby und I18n.transliterate lösen kann.

Iconv

Iconv ist eine Schnittstelle zum Konvertieren von Zeichenketten von einer Kodierung in eine andere.

In dem folgenden Beispiel wird die Zeichenkette von UTF-8 nach CP1252 konvertiert. Nicht vorhandene Zeichen werden durch Ersatzzeichen repräsentiert (transliterate).

$ require 'iconv'
true

$ Iconv.conv('CP1252//translit', 'UTF-8', 'Český Krumlov')
"Cesk\xFD Krumlov"

Aus Č wird ein C und aus ý wird \xFD (\xFD = ý in CP1252).

Iconv ist seit Ruby 1.9.3 “deprecated” und wurde in Ruby 2.0.0 entfernt. Man benötigt also einen Ersatz. Hallo I18n.

I18n

I18n ist eine Ruby-Bibliothek zur Internationalisierung.

#transliterate

Die Methode #transliterate ersetzt Nicht-ASCII-Zeichen durch ein ähnliches ASCII-Zeichen. Wie das konkret aussieht, sieht man im folgenden Beispiel:

$ require 'i18n'
true

$ I18n.transliterate('Český Krumlov')
"Cesky Krumlov"

Aus Č wird ein C und aus ý ein y.

Von UTF-8 zu CP1252

Würde man in unserem Beispiel #encode aufrufen, um die Zeichenkette von UTF-8 in CP1252 zu konvertieren, erhält man folgende Fehlermeldung, da nicht alle Zeichen im CP1252-Zeichensatz enthalten sind:

$ 'Český Krumlov'.encode('CP1252', 'UTF-8')
Encoding::UndefinedConversionError: U+010C to WINDOWS-1252 in conversion from
UTF-8 to WINDOWS-1252

Jetzt kommt I18n.transliterate zum Einsatz. Ich habe eine kleine Methode geschrieben, die #encode und #transliterate kombiniert. Natürlich können die Zeichensätze beliebig ausgetauscht werden.

require 'i18n'

def convert_utf8_to_cp1252_with_transliteration(str)
  str.chars.map do |char|
    # Verwendet Ersatzzeichen für alle Zeichen, die nicht in CP1252 enthalten
    # sind.
    char.encode('CP1252', 'UTF-8', invalid: :replace, undef: :replace,
                replace: I18n.transliterate(char))
  end.join
end

Ich iteriere über jedes Zeichen und versuche, dieses in den angegebenen Zeichensatz zu konvertieren. Wenn das Zeichen nicht in dem Zeichensatz vorhanden ist, wird I18n.transliterate aufgerufen und das Zeichen mit einem ähnlichen ersetzt.

$ convert_utf8_to_cp1252_with_transliteration('Český Krumlov')
"Cesk\xFD Krumlov"

Mittels dieser Methode erhält man wieder das gleiche Ergebnis, wie in dem am Anfang gezeigten Beispiel mit Iconv.