Zeichenkodierungen oder „Warum funktionieren meine Umlaute nicht?”
Dieser Artikel beschreibt die verschiedenen Zeichenkodierungen, wie sie zu Problemen führen können, und wie man sie in Perl-Programmen korrekt berücksichtigt.
Inhaltsverzeichnis
- Einführung
- ASCII
- Andere Zeichenkodierungen
- Unicode
- Was sind „Charsets”?
- Perl und Zeichenkodierungen
- Die Arbeitsumgebung testen
- Troubleshooting
- Kodierungen im WWW
- Weiterführende Themen
- Links
Einführung
Jeder hat es schon mindestens einmal erlebt: Ein Programm, das mit Text arbeitet, funktioniert wunderbar, solange man keine Umlaute eingibt. Sonst kommt nur noch Zeichenmüll heraus und ein bis zwei nicht korrekt dargestellte Zeichen pro Umlaut.
ASCII
Um zu verstehen, warum es dazu kommt, muss man sich anschauen, wie „normaler” Text und wie Umlaute binär abgespeichert werden.
Angefangen hat es 1963 mit ASCII, einem Standard, der 128 Zeichen je eine Zahl von 0 bis 127 zuweist, die mit 7 bit kodiert werden können.
Festgelegt sind die Zahlenwerte für lateinische Buchstaben, Ziffern, Satzzeichen und Kontrollzeichen wie „Carriage Return” und „Line Feed”, also Zeilenumbrüche.
Zeichen, die im Alltag eines Amerikaners nicht vorkommen, wie die deutschen Umlaute, kyrillische Zeichen und vieles mehr, wurden ausser Acht gelassen.
Da ein Byte aus 8 Bits besteht, ist bei ASCII das erste, „most significant” Bit immer 0.
Andere Zeichenkodierungen
Als man in Europa anfing Computer zu benutzen, mussten die benötigten Zeichen irgendwie im Computer gespeichert werden und dazu benutzte man die verbleibenden 128 Zeichen pro Byte. So entstanden die Kodierungen Latin-1 für den westeuropäischen Raum, Latin-2 für Mitteleuropa und so weiter, auch bekannt als ISO-8859-1 und ISO-8859-2.
Diese Zeichensätze stimmen in den ersten 128 Zeichen mit ASCII überein, die zweiten 128 Zeichen, also die mit 1 als erstem Bit, unterscheiden sich jeweils untereinander.
Die Grenzen dieser Zeichensätze werden einem schnell anhand eines gar
nicht so alten Beispieles klar: Mit der Einführung des Euros hatten viele
Länder eine neue Währung und damit ein Währungssymbol, das sich nicht in
den traditionellen Zeichensätzen ausdrücken ließ! (Dieses Problem wurde
durch das Einführen des Zeichensatzes ISO-8859-15 behoben, der sich nur
wenig von Latin-1 unterscheidet und das €
-Zeichen
enthält).
Unicode
Die bisherigen Zeichenkodierungen konnten jeweils nur einen kleinen, lokal sinnvollen Bereich aller möglicher Zeichen darstellen - sobald man Texte mit gemischten Zeichensätzen verfassen wollte, ging das heillose Chaos los.
Um etwas Ordnung in das Chaos zu bekommen, hat das Unicode-Konsortium damit angefangen, jedem Zeichen, das in irgend einer Schrift in irgend einer Sprache vorkommt, eine eindeutige, ganze Zahl und einen Namen zuzuordnen.
Die Zahl heißt „Codepoint” und wird üblicherweise als vier- oder
sechsstellige, hexadezimale Zahl in der Form U+0041
notiert; der
dazugehörige Name wäre LATIN SMALL LETTER A
.
Neben Buchstaben und anderen „Basiszeichen” gibt es auch Akzentuierungen wie
den z. B. ACCENT, COMBINING ACUTE
, die auf den vorherigen Buchstaben einen
Akzent setzen.
Wenn auf ein Basiszeichen eine Akzentuierung oder andere kombinierende Zeichen folgen, bilden mehrer Codepoints ein logischen Buchstaben, ein sogenanntes Grapheme.
Unicode Transformation Formats
Die bisher vorgestellten Unicode-Konzepte stehen vollständig unabhängig davon, wie die Unicode-Zeichen kodiert werden.
Dafür wurden die „Unicode Transformation Formats” definiert, Zeichenkodierungen, die alle möglichen Unicode-Zeichen darstellen können. Der bekannteste Vertreter ist UTF-8, das für die bisher vergebenen Codepoints 1 bis 4 Bytes benötigt.
Auch in UTF-8 stimmen die ersten 128 Zeichen mit denen von ASCII überein.
Von UTF-8 gibt es auch eine laxe Variante, UTF8 (ohne Bindestrich geschrieben), die mehrere mögliche Kodierungen für ein Zeichen zulässt. Das Perl-Modul Encode unterscheidet diese Varianten.
UTF-16 dagegen benutzt für jedes Zeichen mindestens zwei Byte, für sehr hohe Unicode-Codepoints werden auch hier mehr Bytes benötigt.
UTF-32 kodiert jedes mögliche Zeichen mit vier Bytes.
Codepoint | Zeichen | ASCII | UTF-8 | Latin-1 | ISO-8859-15 | UTF-16 |
---|---|---|---|---|---|---|
U+0041 | A | 0x41 | 0x41 | 0x41 | 0x41 | 0x00 0x41 |
U+00c4 | Ä | - | 0xc3 0x84 | 0xc4 | 0xc4 | 0x00 0xc4 |
U+20AC | € | - | 0xe2 0x82 0xac | - | 0xa4 | 0x20 0xac |
U+c218 | 수 | - | 0xec 0x88 0x98 | - | - | 0xc2 0x18 |
(Das Zeichen in der letzten Zeile ist das Hangul-Zeichen für die Silbe SU, und wird von Ihrem Browser nur dargestellt, wenn Sie entsprechende asiatische Schriftarten installiert haben.)
Was sind „Charsets”?
Das Wort charset wird mit zwei verschiedenen, zum Teil widersprüchlichen Bedeutungen verwendet.
Es kommt aus dem Englischen, und ist eigentlich eine Abkürzung für character set. Set ist Englisch für Menge, wenn man es wörtlich nimmt ist also ein Repertoir von Zeichen gemeint, wie Unicode eines definiert. Allerdings schränken auch Kodierungen wie ASCII oder Latin-1 den Zeichensatz ein, stellen also neben Kodierungen auch ein Repertoire oder charset da.
Häufig wird charset auch einfach als Synonym für Zeichenkodierung benutzt, wie zum Beispiel in den HTTP-Headern, die weiter unten noch einmal erwähnt werden.
Perl und Zeichenkodierungen
Perl unterscheidet bei Operationen auf Strings zwischen solchen, die
die Strings als Text betrachten (wie z.B. uc
,
lc
und substr
sowie reguläre Ausdrücke),
und solche, die Strings als Binärdaten betrachten, wie etwa
print
und das Lesen aus Dateihandles.
Damit Perl für die Textoperationen die Strings richtig interpretieren
kann, muss man sie dekodieren. Das kann man mit dem der Funktion
decode
in dem Core-Modul Encode machen, oder
mit den unten beschriebenen IO-Layern.
Umgekehrt muss man Strings mit Encode::encode
kodieren, um binäre
Operationen wie print
auszuführen.
Alle Textoperationen sollte man auf nur auf Strings ausführen, die vorher
dekodiert wurden, weil dann auch
Nicht-ASCII-Zeichen korrekt behandelt werden: lc
und
uc
funktionieren wie erwartet, und \w
in
regulären Ausdrücken passt auf jeden Buchstaben, auch auf Umlaute,
ß
und allen möglichen Zeichen in allen möglichen
Sprachen, die dort als Bestandteil eines Wortes angesehen werden.
cmp
vergleicht Nicht-ASCII-Zeichen allerdings
nach Unicode-Codepoint, was nicht immer das ist, was man z.B. in
deutschem Text erwartet. Nur wenn use locale
aktiv ist,
werden sprachspezifische Vergleichsregeln benutzt.
Da das Verhalten von sort
durch cmp
definiert
ist, gilt dies auch für das Sortieren von Listen.
#!/usr/bin/perl use warnings; use strict; use Encode qw(encode decode); my $enc = 'utf-8'; # in dieser Kodierung ist das Script gespeichert my $byte_str = "Ä\n"; # Bytestrings: print lc $byte_str; # gibt 'Ä' aus, lc hat nichts verändert # Textstrings: my $text_str = decode($enc, $byte_str); $text_str = lc $text_str; print encode($enc, $text_str); # gibt 'ä' aus, lc hat gewirkt
Es empfiehlt sich, alle Eingaben direkt zu dekodieren, dann mit diesen Strings zu arbeiten, und sie erst bei der Ausgabe (oder beim Speichern) wieder in Bytestrings zu kodieren. Wenn man sich nicht an diese Regel hält, verliert man im Programm schnell den Überblick welcher String ein bereits dekodiert wurde und welcher nicht.
Perl bietet mit den IO-Layern Mechanismen, mit denen man das kodieren und dekodieren an Dateihandles oder global an allen Handles durchführen lassen kann.
# IO-Layer: $handle liefert beim Lesen jetzt Textstrings: open my $handle, '<:encoding(UTF-8)', $datei; # das Gleiche: open my $handle, '<', $datei; binmode $handle, ':encoding(UTF-8)'; # Jedes open() soll automatich :encoding(iso-8859-1) benutzen: use open ':encoding(iso-8859-1)'; # Alle Stringkonstanten werden als utf-8 interpretiert # und automatisch dekodiert use utf8; # Schreibe Text mit der aktuellen locale nach STDOUT: use PerlIO::locale; binmode STDOUT, ':locale'; # alle Lese-/Schreibeoperation mit aktueller locale: use open ':locale';
Mit Vorsicht sollte man den Input Layer :utf8
genießen,
der annimmt, dass die Eingabedatei gültiges UTF-8 ist.
Sollte sie das nicht sein (und man hat keine Möglichkeit, das zu
überprüfen), ist das eine potentielle Quelle für Sicherheitslücken (siehe
diesen Artikel auf
Perlmonks für Details).
Das Modul und Pragma utf8 erlaubt es auch, Nicht-ASCII-Zeichen in Variablennamen zu verwenden. In Namespaces und Modulenamen sollte man davon absehen, da es dort nicht zuverlässig funktioniert. Auch sollte man immer beachten, dass nicht jeder die Möglichkeit hat, beliebige Unicode-Zeichen mit der Tastatur einzugeben.
Die Arbeitsumgebung testen
Ausgestattet mit diesem Wissen kann man testen, ob das Terminal und locales auf die gleiche Kodierung eingestellt sind, und auf welche:
#!/usr/bin/perl use warnings; use strict; use Encode; my @charsets = qw(utf-8 latin1 iso-8859-15 utf-16); my $test = 'Ue: ' . chr(220) .'; Euro: '. chr(8364) . "\n"; for (@charsets){ print "$_: " . encode($_, $test); }
Wenn man dieses Programm in einem Terminal ausführt, wird nur eine Textzeile korrekt angezeigt werden, die erste Spalte darin ist dann die Zeichenkodierung des Terminals.
Wie vorher gesagt ist das Eurozeichen €
nicht in Latin-1 vorhanden, das Ü
sollte in einem Latin-1-Terminal trotzdem richtig angezeigt werden.
In Windowsterminals sind auch die Zeichenkodierungen cp850
und
cp858
(die nur von neuen Encode-Versionen unterstützt wird)
üblich, der Rest der Betriebsumgebung benutzt Windows-1252
.
Um den obigen Test auf alle in Perl verfügbaren Zeichenkodierungen
auszuweiten, kann man die Liste der Kodierungen durch
Encode->encodings(':all')
ersetzen.
Troubleshooting
"Wide Character" - Warnungen
Manchmal stolpert man über "Wide character in print"
und
ähnliche Warnungen. Sie bedeuten, dass ein vorher dekodierter String, der
intern als UTF-8 gespeichert wurde, für eine Operation benutzt wurde,
in der nur binäre Daten sinnvoll sind.
Abhilfe schafft es, den String vorher mit Encode::encode
oder
einem entsprechenden Output-Layer zu kodieren.
Strings Untersuchen
Leider dokumentieren viele Module nicht, welche Art von Daten sie zurückliefert, also ob sie bereits dekodiert wurden oder nicht.
Im Allgemeinen ist das auch nicht durch eine Analyse der Strings herauszufinden, da Perl 5 keine getrennten Datentypen für dekodierte und für binäre Strings hat.
Es gibt aber eine Heuristik, die manchmal hilft. Dazu benötigt man das Modul Devel::Peek:
use Devel::Peek; use Encode; my $str = "ä"; Dump $str; $str = decode("utf-8", $str); Dump $str; Dump encode('latin1', $str); __END__ SV = PV(0x814fb00) at 0x814f678 REFCNT = 1 FLAGS = (PADBUSY,PADMY,POK,pPOK) PV = 0x81654f8 "\303\244"\0 CUR = 2 LEN = 4 SV = PV(0x814fb00) at 0x814f678 REFCNT = 1 FLAGS = (PADBUSY,PADMY,POK,pPOK,UTF8) PV = 0x817fcf8 "\303\244"\0 [UTF8 "\x{e4}"] CUR = 2 LEN = 4 SV = PV(0x814fb00) at 0x81b7f94 REFCNT = 1 FLAGS = (TEMP,POK,pPOK) PV = 0x8203868 "\344"\0 CUR = 1 LEN = 4
Der String UTF8
in der Zeile FLAGS =
zeigt,
dass der String intern als UTF-8 gespeichert wird und bereits dekodiert
wurde. In der Zeile PV =
sieht man bei solchen Strings die
Bytes und in eckigen Klammern die Codepoints.
Allerdings ist der Umkehrschluss nicht zulässig: das Fehlen des UTF8 Flags bedeutet keineswegs, dass der String vorher nicht dekodiert wurde. Es bedeutet lediglich, dass perl für Textoperationen die Kodierung Latin-1 annimmt.
Fehlerhafte Module
Weitere Probleme können durch fehlerhafte Module entstehen. So ist die Funktionalität des Pragmas encoding sehr verlockend:
# automatische Konvertierungen: use encoding ':locale';
Allerdings funktionieren unter dem Einfluss von use encoding
AUTOLOAD-Funktionen nicht mehr, und das Modul funktioniert nicht im
Zusammenspiel mit Threads.
Kodierungen im WWW
Beim Schreiben von CGI-Scripten muss man sich überlegen in welcher Kodierung die Daten ausgegeben werden sollen und das entsprechend im HTTP-Header vermerken.
Für die meisten Anwendungen empfiehlt sich UTF-8, da man damit einerseits beliebge Unicode-Zeichen kodieren kann, andererseits auch deutschen Text platzsparend darstellen kann.
HTTP bietet zwar mit dem Accept-Charset
-Header eine Möglichkeit
herauszufinden, ob ein Browser mit einer Zeichenkodierung etwas anfangen kann,
aber wenn man sich an die gängigen Kodierungen hält, ist es in der Praxis
nicht nötig, diesen Header zu prüfen.
Für HTML-Dateien sieht ein Header typischerweise so aus:
Content-Type: text/html; charset=UTF-8
. Wenn man einen solchen Header
sendet, muss man im HTML-Code nur die Zeichen escapen, die in HTML eine
Sonderbedeutung haben (<
, >
, &
und innerhalb von Attributen
auch "
).
Zu beachten ist auch, dass der HTTP-Header (und damit auch URLs) nur
ASCII-Zeichen enthalten darf, d.h. URLs und Cookies müssen nach ASCII
kodiert werden. Üblich ist es, die Daten in UTF-8 umzuwandeln, und alle
Bytewerte größer als 127 (und solche die dort nicht erlaubt sind, z.B.
Leerzeichen in URLs) als Prozentzeichen gefolgt vom zweistelligen
Hexadezimalwert des Bytes zu kodieren. Aus einem Leerzeichen wird dabei
%20
, aus einem Ä wird %c4%84
.
Beim Einlesen von POST- oder GET-Parametern mit dem Modul CGI muss man
darauf achten, welche Version man benutzt: In älteren Versionen liefert die
param
-Methode immer Bytestrings zurück, in neueren Versionen (ab 3.29)
werden die Strings dekodiert zurückgegeben, wenn vorher mit
charset
die
Zeichenkodierung UTF-8 eingestellt wurde - andere Kodierungen werden von
CGI nicht unterstützt.
Damit Formularinhalte vom Browser mit bekanntem Zeichensatz abgeschickt
werden, gibt man im Formular das accept-charset
-Attribut mit an:
<form method="post" accept-charset="utf-8" action="/script.pl">
Bei Verwendung eines Template-Systems sollte man darauf achten, dass es
mit Zeichenkodierungen umgehen kann. Beispiele sind Template::Alloy,
HTML::Template::Compiled
(seit Version 0.90 mit der Option open_mode
) oder
Template Toolkit in Verbindung mit
Template::Provider::Encoding.
Weiterführende Themen
Mit den Grundlagen zu den Themen Zeichenkodierungen und Perl kommt man schon sehr weit, zum Beispiel kann man Webanwendunen „Unicode-Safe” machen, also dafür sorgen, dass alle möglichen Zeichen vom Benutzer eingegeben und dargestellt werden können.
Damit ist aber noch längst nicht alles auf diesem Gebiet gesagt. Der Unicode-Standard erlaubt es beispielsweise, bestimmte Zeichen auf verschiedene Arten zu kodieren. Um Strings korrekt miteinander zu vergleichen, muss man sie vorher „normalisieren”. Mehr dazu gibt es in der Unicode-Normalisierungs-FAQ.
Um landesspezifisches Verhalten für Programme zu implementieren, lohnt es, die
locales genauer anzusehen.
Im Türkischen z.B. wird lc 'I'
zu
ı, U+0131 LATIN SMALL LETTER DOTLESS I
,
während uc 'i'
zu İ,
U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
wird..
Ein guter Einstiegspunkt in die Locales ist das Dokument
perllocale.
Links
- Grundlagen der Zeichenkokdierungen ausführlich erklärt
- Das selfhtml-wiki diskutiert Zeichensatz im Zusammenhang mit HTTP und HTML.
Dieser Artikel wurde für das $foo-Magazin geschrieben und in der 5. Ausgabe (Anfang 2008) veröffentlicht.