Kommen Umlaute im Programmtext oder Datenmaterial ins Spiel, muss der Perl-Programmierer mithelfen, damit kein Kodierungskauderwelsch herauskommt.
Am Anfang war die ASCII-Tabelle: 128 Zeichen, mit denen man problemlos
englische Texte schreiben konnte, mitsamt auf Schreibmaschinen
gängigen Sonderzeichen wie %
und $
, und natürlich Kontrollzeichen
wie der Zeilenumbruch, der Seitenvorschub und die Terminalklingel.
Als dann einige Europäer die in ihren Sprachen
heimischen Umlaute einbringen wollten, wurden diese in
die nachfolgenden 128 Zeichen gepresst.
Alle 256 Zeichen ließen sich von 0 bis 255 durchnummerieren
und auf dem Computer mit acht Bits (einem Byte) kodieren.
Der ISO-8859-Standard (oder salopper: Latin-1) war geboren. Es fing an mit ISO-8859-1, aber mit der Zeit kamen weitere Varianten hinzu. Die bislang letzte Version ist ISO-8859-15, die auch das Euro-Zeichen enthält. Übrigens verwenden die meisten heutzutage verwendeten Webbrowser nicht den ISO-8859-1-Standard zur Dekodierung, wenn der Webserver ISO-8859-1 schickt. Statt dessen arbeiten sie nach dem Windows-1252-Standard, der einige zusätzliche Zeichen wie zum Beispiel das Eurozeichen definiert.
Bald wollte auch die restliche Welt teilhaben und ihre zum Teil voluminösen Zeichensätze von Computern darstellen lassen. So wurden weitere Kodierungsschemata für asiatische Schriften wie Shift-JIS und BIG5 erfunden. Dann sah man ein, dass das eine Sackgasse war und erfand Unicode, eine riesige Tabelle, die alle gängigen Zeichen aller Weltsprachen enthält.
Ein Weg, die Unicode-Tabelle auf dem Computer effektiv darzustellen, ist der UTF-8-Standard. Denn hätten herkömmliche ASCII-Zeichen plötzlich durchgehend mit zwei oder gar vier Bytes kodiert werden müssen, wäre der Platzbedarf sprunghaft angestiegen. Um die Zeichen der ollen ASCII-Tabelle weiterhin mit einem Byte darstellen zu können, wurde die UTF-8-Tabelle so angelegt, dass die ersten 128 Zeichen genau der ASCII-Tabelle entsprechen. Die zweite Hälfte der untersten 256 Zeichen besteht jedoch zum Teil aus speziellen Maskierungs-Codes, die anzeigen, dass nach ihnen eine definierte Anzahl weiterer Codes folgen, die das darzustellende Zeichen aus der Unicode-Tabelle eindeutig festlegen.
So ist zum Beispiel der Umlaut ü
in der ISO-8859-15-Tabelle unter
der Nummer 252 (0xFC
) abgelegt. Liegt also ein Text in ISO-8859-15 vor,
und ein Byte hat den Wert 0xFC
, dann ist klar: Das Zeichen an dieser
Stelle ist ein ü
.
In der UTF-8-Kodierung hingegen wird der Umlaut
ü
mit den zwei Bytes 195 und 188 (0xC3
und 0xBC
)
dargestellt. Liegt ein Text in UTF-8 vor, und der Computer stösst
erst auf einen Bytewert von 0xC3
, gefolgt von einem 0xBC
,
ist klar: Dort steht der Buchstabe ü
.
Ist aber die Kodierung eines Bytestromes nicht bekannt, und der
Computer stößt auf ein Byte mit dem Wert 0xC3
, stellt sich
die Frage: Ist dies das erste Byte eines westeuropäischen Umlauts
im UTF-8-Format? Oder ist das Byte nach ISO-8859-15 zu interpretieren
und ist bereits die vollständige Kodierung eines Zeichens?
Wird das einleitende Byte 0xC3
einer UTF-8-Sequenz fälschlicherweise
nach ISO-8859-15 interpretiert, kommt ein Zeichen zum Vorschein, das
leidgeplagte Programmierer, die zwischen den Kodierungswelten wandeln,
zum Überdruss kennen: Das A mit Welle.
Dieses in Abbildung 1 gezeigte Zeichen kommt nach [2] in der portugiesischen, vietnamesischen oder kaschubischen Schrift vor. Operiert man normalerweise nicht in diesen Gefielden, erblickt aber ein A mit Welle, handelt es sich höchstwahrscheinlich um einen in UTF-8 kodierten Text, der irrtümlich nach ISO-8859-15 interpretiert wurde.
Abbildung 1: In einem auf ISO-8859-15 eingestellten Terminal funktioniert die Ausgabe in Latin1, aber Ausgaben in UTF-8 ergeben Kaschubisch. |
Abbildung 2: In einem auf UTF-8 eingestellten Terminal funktioniert die Ausgabe in UTF-8, aber in ISO-8859-15 kodierte Zeichen bleiben unsichtbar. |
Ein Terminal, das die Textausgabe eines Programms auf den Bildschirm bringt, muss ebenfalls wissen, wie der vom Programm ausgesandte Bytestrom zu interpretieren ist und muss dann die zur Anzeige geeigneten Zeichen finden.
Um ein X-Terminal mit UTF-8 zu starten, empfiehlt
es sich, xterm
mit den Optionen -u8 +lc
aufzurufen. Die Option
-u8
schaltet UTF-8 an und +lc
schaltet die Interpretation
von Shell-Variablen wie LANG
ab, damit diese nicht dazwischenfunken.
Um ein Terminal im ISO-8859-15-Modus zu öffnen, wird xterm
mit gesetzter Environment-Variable LANG
aufgerufen:
LANG=en_US.ISO-8859-15 xterm
Die Abbildungen 1 und 2 zeigen die Ausgabe zweier Perlskripte
in einem ISO-Terminal und in einem UTF-8-Terminal.
Ein einzelnes Byte mit dem Wert 0xFC
wird vom roten ISO-Terminal korrekt
als ü
interpretiert. Aus der UTF-8-Sequenz 0xC3BC
hingegen wird ein A mit aufgesetzter Tilde und der ISO-Darstellung
des Codes 0xBC
, dem Zeichen für 1/4
.
Im grünen UTF-8-Terminal wird ein einzelnes Byte mit dem Wert 0xFC
hingegen nicht angezeigt. Und erwartungsgemäß wird die UTF-8-Sequenz
0xC3BC
korrekt zu einem ü
dekodiert.
Gibt man nichts explizit vor, interpretiert Perl den Sourcecode
eines Skripts
einschließlich aller Strings, regulärer Ausdrücke, Variablen-
und Funktionsnamen als ISO-8859-1-kodiert. Wird der Code in Listing
latin1
mit einem Editor erstellt, der auf ISO-8859-1 eingestellt ist,
liegt das ü im Programmtext als 0xFC
vor, wie sich
leicht mit der Utility hexdump
feststellen lässt (Abbildung 3).
1 #!/usr/bin/perl -w 2 use strict; 3 my $s = "ü"; 4 print "umlaut=$s", "\n";
Abbildung 3: In einem in ISO-8859 geschriebenen Perlskript wird das Zeichen ü im Sourcecode als 0xFC kodiert. Ist der Sourcecode in UTF-8 kodiert, wird das ü hingegen von der Bytesequenz 0xC3BC dargestellt. |
Listing eg/utf8
hingegen wurde mit dem Editor vim
und
der Einstellung set encoding=utf-8
erstellt. Die rot markierten
Stellen im Hexdump von Abbildung 3 zeigen,
dass der Umlaut im String des Programmcodes
tatsächlich mit zwei Bytes kodiert wurde, C3
und BC
.
1 #!/usr/bin/perl -w 2 use strict; 3 use utf8; 4 my $s = "ü"; 5 6 binmode STDOUT, ":utf8"; 7 print "umlaut=$s", "\n";
In Listing utf8
fallen noch zwei zusätzliche Zeilen auf:
Zunächst wurde das Pragma use utf8
gesetzt, damit Perl den
Sourcecode des Skripts als UTF-8 interpretiert. So wird aus dem
String "ü"
, der ja als 0xC3BC
im Sourcecode steht, als
String mit einem einzigen Zeichen, dem Unicode-Zeichen ü
, erfasst.
Entsprechend gäbe length($s)
nicht 2, sondern tatsächlich 1
zurück.
Und zweitens setzt Listing utf8
die ``Line Discipline'' der
Standardausgabe mit binmode(STDOUT, ":utf8")
in den UTF-8-Modus.
Dies bewirkt, dass Perl Unicode-Strings im UTF-8-Format über die
Standardausgabe ausgibt. So erhält das Terminal die erwarteten
UTF-8-Daten und stellt sie ordnungsgemäß dar.
Fehlt der binmode
-Aufruf, versucht Perl, den ausgegebenen
String nach Latin-1 umzuwandeln. Auf einem ISO-8859-1-Terminal
ist das sogar sinnvoll, doch auf einem UTF-8-Terminal ist das
natürlich genau die falsche Strategie.
Und es geht komplett schief, wenn
das Unicode-Zeichen nicht in Latin-1 umzuwandeln ist, wie zum
Beispiel das japanische Katakana-Zeichen ``me'' mit der Unicode-
Nummer 30E1
. In diesem Fall ertönt die Warnung: ``Wide
character in print''. Eine mit binmode(STDOUT, ":utf8")
eingestellte Line-Discpline auf dem ausgebenden Filehandle sorgt
dafür, dass Perl keinerlei Anstalten macht, den Unicode-String
umzuwandeln, sondern ihn roh als UTF-8 ausgibt. Nutzt jemand ein
UTF-8-Terminal, ist das genau die richtige Strategie.
Die Manualseite man iso-8859-1
zeigt die Codierung nach dem
Latin-1-Standard an.
Dort findet sich unter der oktalen Nummer 374 der
Hex-Eintrag FC
mit dem Zeichen LATIN SMALL LETTER U WITH DIAERESIS
,
dem Umlaut ü
. Um hingegen die Unicode-Tabelle aufzublättern, eignet sich
die Datei unicore/UnicodeData.txt
, die sich unterhalb des
lib
-Verzeichnisses der Perldistribution befindet
(normalerweise ls /usr/lib/perl5/5.8.x
). Dort findet sich
unter der Nummer 00FC
ebenfalls ein
Eintrag LATIN SMALL LETTER U WITH DIAERESIS
.
Abbildung 4: Der Eintrag für "ü" in Perls Unicode-Tabelle. |
Dem aufmerksamen Leser wird beim Studieren der Abbildung 4 auffallen,
dass die Ordnungsnummer
des kleinen ü
in der Unicode-Tabelle exakt dem Eintrag des kleinen
ü
s in der ISO-8859-1-Tabelle entspricht: Es ist beidesmal FC
(FC
in ISO-8859-1 und 00FC
in Unicode),
da die Ersteller der Unicode-Tabelle sich bei den untersten 256 Bytes
nach dem ISO-8859-1-Standard orientierten.
Zu beachten ist allerdings dass die Unicode-Nummer nicht der UTF-8-
Kodierung des Zeichens entspricht.
So hat das ü
, das Unicode-Zeichen mit der Nummer 00FC
, die
UTF-8 Kodierung C3BC
. Wie bereits erwähnt, ist UTF-8 nur ein
effizientes Verfahren, die Unicode-Tabelle zu kodieren.
Hat man kein Keyboard mit Umlauten zur Verfügung (wie der Schreiber
dieser Zeilen im fernen San Francisco), lässt sich ein ü
in einem
String auch über seine Unicode-Nummer
my $s = "\x{00FC}";
eintippen. Alternativ bieten Editoren Tastenkombinationen an. Im
populären Editor vim
lautet die Tastenfolge für ein kleines
ü
im Input-Modus "CTRL-k u :"
und es ist zu beachten, dass
vim
mit set encoding=utf-8
auf UTF-8 eingestellt ist.
Liest ein Perlprogramm Daten ein oder gibt sie aus, muss der
Programmierer festlegen, in welchem Format die Daten vorliegen
oder ausgegeben werden.
Um Zeilen einer als UTF-8 vorliegenden Textdatei einzulesen,
kann man das genutzte Filehandle FILE
entweder mit dem oben
gezeigten binmode
-Trick auf :utf8
ummünzen oder die Line-Discipline
gleich dem dreiparametrigen open
-Befehl mitgeben:
open FILE, "<:utf8", "datei.txt" ...
Liest das Programm anschließend eine Dateizeile mit
<FILE>
ein und weist das Ergebnis einem Skalar zu, ist
sichergestellt, dass der String a) ein Unicode-String ist und b) Perl
sich eine interne Notiz von dieser Tatsache macht.
Ohne Line-Discipline würde die Eingabe als ISO-8859-1 interpretiert
werden und Perl würde die rohen Bytes in herkömmliche String-Skalare
stopfen.
Analoges gilt für die Ausgabe. ``>:utf8'' oder ``>>:utf8'' als zweiter
Parameter für open
setzt die Line-Discipline der Ausgabe
in den UTF-8-Modus und ein folgendes print FILE $string
wird den
UTF-8-kodierten String unmodifiziert ausgeben. Alternativ kann auch hier
das Filehandle mit binmode
umgemünzt werden.
Listing peek
zeigt, wie Perl Unicode-Strings intern verwaltet.
Wegen des gesetzten Pragmas use utf8
wird der String "ü"
(der mit vim in utf-8 eingetippt wurde)
als Unicode-String erkannt und verwaltet. Hierzu setzt Perl ein
internes Flag, das sich mit dem Modul Encode abfragen (is_utf8()
)
und manipulieren (_utf8_off()
) lässt.
Die Ausgabe von peek
in Abbildung 4 zeigt, dass der UTF-8-String
tatsächlich die Länge 1 aufweist.
01 #!/usr/bin/perl -w 02 use strict; 03 use utf8; 04 use Data::Hexdumper; 05 use Encode qw(_utf8_off is_utf8); 06 07 my $s = "ü"; 08 09 if( is_utf8($s) ) { 10 print "UTF-8 flag is 'on'.\n"; 11 } 12 13 print "Len: ", length($s), "\n"; 14 _utf8_off($s); 15 print "Len: ", length($s), "\n"; 16 17 print hexdump(data => $s), "\n";
Abbildung 5: In einem Unicode-String hat ein Multi-Byte-Zeichen tatsächlich die Länge eins. Wird dem String die Unicode-Eigenschaft entzogen, interpretiert Perl ihn Byte für Byte. |
Wird das Flag mit _utf8_off()
gelöscht, ist der String plötzlich
zwei Zeichen lang. Und die Ausgabe mit dem CPAN-Modul
Data::Hexdumper
zeigt, dass der String intern als 0xC3BC
gespeichert wird -- und das ist (Trommelwirbel) tatsächlich UTF-8.
Listing isotest.cgi
zeigt, wie ein CGI-Skript dem Browser
nach ISO-8859-1 kodierten Text verspricht und dann ein Euro-Zeichen
mit dem Code 0x80 schickt, der dem Windows-1252-Standard entspricht.
Wie aus Abbildung 6 ersichtlich, gibt sich der Browser jedoch kulant und zeigt das Euro-Zeichen tatsächlich an. Hätte das Server-Skript jedoch statt dessen im vorauseilenden Header ISO-8859-15 signalisiert, stünde ein schwarzes Fragezeichen anstelle des Euros auf der vom Browser dargestellten Seite. In der ISO-8859-15-Tabelle hat der Euro nämlich den Code 0xA4. Wird der Code dahingehend verändert, zeigt der Browser das Eurozeichen wieder korrekt an.
01 #!/usr/bin/perl -w 02 use strict; 03 use CGI qw(:all); 04 05 print header( 06 -type => 'text/html', 07 -charset => 'iso-8859-1'); 08 09 print "The Euro sign is ", 10 chr(0x80), ".\n";
Abbildung 6: Der Browser zeigt |
Perls Webclient-Bibliothek LWP gibt sich allerdings nicht so kulant.
Deswegen wollen wir nun Listing isotest2.cgi
betrachten, das den
Text ordnungsgemäß als UTF-8 schickt und auch den Header der Antwort
richtig setzt.
Das Eurozeichen im String wird hier durch seine Unicode-Ordnungsnummer
\x{20AC}
angegeben.
01 #!/usr/bin/perl -w 02 use strict; 03 use CGI qw(:all); 04 05 print header( 06 -type => 'text/html', 07 -charset => 'utf-8'); 08 09 binmode STDOUT, ":utf8"; 10 print "The Euro sign is ", 11 "\x{20AC}.\n";
Doch auch auf der Client-Seite einer Web-Applikation gilt es eine Reihe
von Dingen zu beachten, wenn der vom Server kommende Webseitentext
in UTF-8 kodiert ist. Das Ziel ist es, das Dokument mit der LWP-Bibliothek
vom Webserver zu holen und den Inhalt im Erfolgsfall in einem
Unicode-String in Perl abzulegen. Listing webclient
zeigt das
Verfahren.
Wegen eines offenen Bugs in der LWP-Bibliothek (genauer: HTML::HeadParser)
wirft Perl bei zurückkommendem UTF-8 die unschöne Warnung
Parsing of undecoded UTF-8 will give garbage when decoding entities
aus,
was sich laut [4] durch die Option parse_head => 0
im Konstruktoraufruf
des UserAgent vermeiden lässt.
01 #!/usr/bin/perl -w 02 use strict; 03 use LWP::UserAgent; 04 05 my $ua = LWP::UserAgent->new( 06 parse_head => 0 07 ); 08 09 my $resp = $ua->get( 10 "http://perlmeister.com/cgi/isotest.cgi"); 11 12 if($resp->is_success()) { 13 my $text = $resp->decoded_content(); 14 binmode STDOUT, ":utf8"; 15 print "$text\n"; 16 }
Damit Perl den zurückkommenden UTF-8-Text in einem Unicode-String ablegt,
wird der Seitentext nicht mit der sonst üblichen Methode content()
aus dem HTTP::Response-Objekt
extrahiert, sondern mit decoded_content()
. Diese Methode konsultiert
zur Dekodierung das vom Webserver im Header mitgeschickte charset
-Feld.
Achtet dann der Client auch noch auf die Line-Discipline für STDOUT,
steht der ordnungsgemäßen Ausgabe in einem Terminal im UTF-8-Modus nichts
mehr im Wege.
Historisch bedingt ist also das Wandeln zwischen den Kodierungswelten keine einfache Sache. Wer die Verfügbarkeit seiner Software jedoch nicht künstlich auf die Hälfte des Marktes beschränken willl, sollte seine Internationalisierungsstrategie schleunigst zurechtrücken.
Anmerkung: In den Listings ``utf8'' und ``peek'' ist jeweils ein Umlaut als UTF-8 angegeben, während ich sonst ISO-8859 schicke, bitte entsprechend konvertieren!
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |