Ü auf Umwegen (Linux-Magazin, Juli 2007)

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.

Alle wollen mitspielen

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 ü.

A mit Welle

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.

Terminal muss mitspielen

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.

Latein als Normalfall

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).

Listing 1: latin1

    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.

Listing 2: utf8

    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.

Nachgeblättert

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.

Keyboard ohne Umlaute

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.

Rein und Raus

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.

Die letzte Hülle fällt

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.

Listing 3: peek

    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.

Sagt ISO-8859-1, meint Windows-1252

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.

Listing 4: isotest.cgi

    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

Nicht so kulant

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.

Listing 5: isotest2.cgi

    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.

Listing 6: webclient

    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!

Infos

[1]
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2006/09/Perl

[2]
``The Mystery of the Double-Encoded UTF8'', http://blog.360.yahoo.com/blog-8_S91Lc7dKj4rz8iueEaVlexawc_ZFVpd4JK?p=16

[3]
``Windows-1252'', http://en.wikipedia.org/wiki/Windows-1252

[4]
Offener LWP-Bug bei UTF-8-kodierten Webseiten: http://www.mail-archive.com/libwww@perl.org/msg06330.html

Michael Schilli

arbeitet 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.