Zeichensätze und Codierungen

(Auszug aus "Perl & XML" von Erik T. Ray & Jason McIntosh)

Wie auch immer Sie die Ausgabe Ihres Programms bewerkstelligen, vergessen Sie nie das Konzept der Zeichensätze und -codierungen – also das von Ihrem XML-Dokument verwendete Protokoll zur Repräsentation der verschiedenen Symbole Ihrer Sprache. Egal, ob es sich um die Buchstaben eines Alphabets handelt, um Ideographen oder diakritische Markierungen. Zeichencodierungen können einer der schwierigsten Aspekte der XML-Programmierung sein. Überraschenderweise gilt das besonders für Programmierer aus Westeuropa oder den Vereinigten Staaten, die einerseits die einfachsten Zeichensätze (ISO 8859-1 bzw. ASCII) haben, dadurch aber am wenigsten Gelegenheit haben, die Tücken der komplizierteren kennenzulernen.

Ein XML-Dokument darf in seiner XML-Deklaration den Namen seiner Zeichensatzcodierung selbst angeben. Das heißt aber keineswegs, daß ein XML-Parser notwendigerweise damit umgehen kann. Die Spezifikation verlangt nur die Unterstützung von UTF-8 und UTF-16, zweier Varianten von Unicode , einer neueren und extrem umfangreichen Zeichensatzarchitektur. Unicode enthält so ziemlich jeden denkbaren Schnörkel als Symbol.

In diesem Abschnitt wollen wir Sie langsam in den Umgang mit Unicode von Perl und XML aus einführen, wenn Sie sich damit nicht bereits auskennen. Nichts spricht dagegen, alles in diesem Buch Beschriebene mit Hilfe des traditionellen Zeichensatzes Ihrer Wahl durchzuführen, aber im Laufe der Zeit werden Sie möglicherweise feststellen, daß Sie damit gegen den Strom schwimmen.

Anmerkung: Wie alles im Leben hat auch die Wahl des Zeichensatzes Vor- und Nachteile. Ich persönlich finde es im Alltag einfacher, mit dem lokalen, deutschen Zeichensatz (d. h. der Codierung ISO 8859-1) zu arbeiten, und habe mich deshalb auch bei den Beispielen dieses Buchs dafür entschieden. Es ist aber unstrittig, daß das nicht in allen Fällen genügt (wußten Sie, daß Sie bereits für die Sprache unseres Nachbarlands Tschechien mit diesem Zeichensatz verloren haben?) und daß eine Kenntnis der Möglichkeiten von Unicode deswegen unerläßlich ist. – Anm. d. Ü.

Unicode, Perl und XML

Unicode hat sich als die Antwort des digitalen Zeitalters auf die Bedürfnisse all der unzähligen Schriften etabliert, die über Jahrhunderte für das Einkommen Tausender von Mönchen und Linguisten gesorgt haben. Wenn Sie sich in einer Umgebung befinden, wo Nicht-ASCII-Zeichen zuhauf vorkommen, kennen Sie Unicode möglicherweise bereits. Unicode soll die Entwicklung in diese Richtung fördern, Perl möchte dabei helfen, und XML baut bereits darauf auf.

Jedes Unicode predigende Dokument wird Ihnen sagen, daß Unicode hervorragend für die Internationalisierung von Quelltexten geeignet ist. Mit einer einzigen Zeichensatzarchitektur kann man ein Programm an nahezu beliebige Sprachumgebungen anpassen.

Die Bedeutung von Unicode steigt sogar noch drastisch, wenn Sie die Frage der Datenrepräsentation ansprechen. Die von einem beliebigen Anwender eines Programms gesprochene Sprache ist nur ein Aspekt. Computer berühren jeden Bereich des Lebens eines jeden Menschen, und einige dieser Menschen sprechen Kurku. Wenn man die Grundlagen von Unicode versteht, dann sieht man auch, wie Unicode bei der Speicherung beliebiger Daten hilft, unabhängig von der Architektur eines Programms.

Codierungssysteme von Unicode

Wir unterscheiden sorgfältig die Begriffe »Architektur« (oder »Zeichensatz«) und »Codierung«. Unicode ist eine Architektur, die verschiedene Codierungen enthält.

Jedes Symbol, das in Unicode Eingang findet (von A über α bis zu ☺) hat seinen eigenen Codierungspunkt – eine eindeutige, positive, ganze Zahl, die die Position des Zeichens auf der Karte von Unicode angibt. Zum Beispiel hat der erste Großbuchstabe des romanischen Alphabets die hexadezimale Adresse 0x0041 (dieselbe wie in ASCII und den damit verwandten Zeichensätzen), die beiden anderen obengenannten Zeichen, das griechische alpha und der Smiley, findet man an den Adressen 0x03B1 und 0x263A. Ein Zeichen kann aus einem einzelnen Codierungspunkt entstehen. Es gibt aber auch Zeichen, die durch die Kombination verschiedener Codierungspunkte gebildet werden. Es gibt Codierungspunkte, die den verschiedenen diakritischen Markierungen gewidmet sind, also den Accent-Zeichen und den Radikalen. Viele Schriften kombinieren diese mit Buchstaben oder ideographischen Bildzeichen.

Diese Adressen sowie die zehntausend anderen (inzwischen wohl eher hunderttausend) Symbole in der Unicode-Tabelle sind bei allen Codierungssystemen von Unicode stets dieselben. Der Unterschied besteht in der Abbildung dieser Zahlen in die Nullen und Einsen, die das Dokument auf unterster Ebene darstellen.

Unicode unterstützt offiziell drei Codierungssysteme. Die Namen beginnen stets mit UTF (Unicode Transformation Format), eine Zahl mit der minimalen Anzahl von Bits pro Zeichen folgt. Die Codierungssysteme heißen dementsprechend UTF-8, UTF-16 und UTF-32. UTF-8 ist die flexibelste und damit diejenige, die von Perl übernommen wurde.

UTF-8

UTF-8 paßt wunderbar zu Perl, weil dieses Codierungssystem einige Tricks anwendet, um die Anzahl der Bytes des entstehenden Dokuments niedrig zu halten. Als einziges Codierungssystem kann es einige Zeichen mit einem einzigen Byte darstellen. UTF-8 ist das Standardcodierungssystem eines XML-Dokuments. Wird in der Deklaration kein eigenes Codierungssystem angegeben, dann sollte der Parser UTF-8 annehmen.

Für jedes einzelne Zeichen eines mit UTF-8 codierten Dokuments wird eine bestimmte Anzahl von Bytes verwendet, die den Codepunkt des Zeichens angeben. Das können bis zu sechs Bytes werden. Das Zeichen A mit der Adresse 0x41 wird durch ein Byte repräsentiert. Unser Freund ☺ dagegen lebt am Ende der Unicode-Straße mit der Adresse 0x263A. In diesem Fall sind drei Bytes erforderlich – zwei für den Codepunkt des Zeichens und eines, das dem Textprozessor signalisiert, daß insgesamt drei Bytes vorhanden sind. In einigen Jahrhunderten, wenn die Erde endlich der Galaktischen Union der Freundschaft beigetreten sein wird, kann Unicode auch die Zeichensätze der verschiedenen Zivilisationen anderer Planeten aufnehmen, und dann werden die Bytes vier bis sechs vermutlich noch gute Dienste leisten.

UTF-16

UTF-16 benutzt in jedem Fall mindestens zwei Bytes pro Zeichen, selbst wenn seine Adresse in ein einzelnes Byte passen würde. (UTF-8 würde das eventuell auch so machen.) Falls die Adresse groß genug ist, kann es aber durchaus passieren, daß zwei weitere Bytes (das sogenannte Surrogat-Paar) angehängt werden. In diesem Fall hat das Zeichen eine Länge von vier Bytes.

Anmerkung: Der ältere Standard Unicode 2.0 kannte nur 16 Bits pro Zeichen. Wenn man den Begriff »Unicode« hört, dann ist damit gelegentlich UTF-16 gemeint. So findet man etwa ab und zu Dialogboxen mit »Speichern als ...«, die die Optionen »Unicode« und »UTF-8« anbieten, obwohl das in der Terminologie von Unicode 3.2 nicht unbedingt verschiedene Dinge sind.

UTF-32

UTF-32 funktioniert wie UTF-16, kennt aber keine variable Anzahl von Bytes mehr. Jede Adresse wird durch exakt vier Bytes repräsentiert. Die so codierten Dokumente werden deshalb sehr groß, so daß dieses Codierungssystem in der Praxis eher Nachteile mit sich bringt. So leidet zum Beispiel die Effizienz der Verarbeitung stark unter den vielen zusätzlichen Nullen. XML-Parser sind nicht gezwungen, UTF-32 zu verstehen. Insofern müssen Sie sich vermutlich auch nicht weiter damit befassen.

Andere Codierungssysteme

Abgesehen von den zwei Codierungssystemen, die ein Parser kennen muß, definiert der XML-Standard weitere 21, die ein Parser beherrschen sollte. Dazu gehören unter anderem ISO-8859-1 (ASCII sowie 128 westeuropäische Zeichen außerhalb des romanischen Alphabets) oder Shift_JIS, ein Codierungssystem von Microsoft für japanische Schriftzeichen. Dies sind zwar keine Unicode-Codierungssysteme, aber für jedes der darin enthaltenen Zeichen gibt es einen entsprechenden Codierungspunkt in Unicode.

Die XML-Parser von Perl haben alle ihre eigene Art und Weise, mit anderen Codierungssystemen umzugehen. Dabei ist gelegentlich ein bißchen Hilfe des Anwenders notwendig. Zum Beispiel ist XML::Parser in diesem Punkt sehr schwach, weil seine zugrundeliegende Bibliothek Expat nur sehr wenige Codierungssysteme beherrscht. Glücklicherweise gibt es dafür mit dem Modul XML::Encoding von Clark Cooper eine gute Lösung. Dabei handelt es sich um eine Subklasse von XML::Parser, die sogenannte Map-Dateien beherrscht. Diese Map-Dateien sind selbst XML-Dokumente, die eine Abbildung von Unicode-Codierungspunkten auf die verschiedenen Zeichen eines Zeichensatzes definieren.

Unterstützung durch den Perl-Kern

Die Romanze von Perl und Unicode hat sich ähnlich wie die von Perl und XML langsam, aber unaufhaltsam entwickelt.

Vielleicht finden Sie diese Metapher etwas zu romantisch, aber man sollte an dieser Stelle bedenken, daß das polygame Wesen von Perl letzten Endes das aus der Sprache gemacht hat, was sie heute ist.

Man sollte Perl 5.8 oder eine neuere Version verwenden, um mit Unicode vernünftig arbeiten zu können. Die perlunicode-Manpage bietet Informationen darüber, wie weit Unicode von der jeweiligen Perl-Version unterstützt wird. Diese Manpage sollten Sie in jedem Fall zu Rate ziehen, weil jede Version weitere Verbesserungen bietet.

Mit einigen Tools aus diesem Abschnitt kann man auch bei älteren Perl-Versionen arbeiten, aber Arbeit mit Perl und XML bedeutet irgendwann Arbeit mit Unicode, und dann werden Sie die mangelnde Unterstützung durch den Perl-Kern bemerken.

Die erste Perl-Version mit (partieller) Unicode-Unterstützung ist Perl 5.6. Das Pragma use utf8 gibt Perl die Anweisung, die meisten seiner Stringbehandlungsfunktionen auf UTF-8 umzustellen. Perl erlaubt in diesem Fall auch in UTF-8 geschriebene Quelltexte. Das beinhaltet Bezeichner mit lokalen Buchstaben, eine sehr schöne Eigenschaft, wenn man eine andere als die romanische Schrift benutzt.

Von Variablennamen wie $länge sollte man trotzdem eher Abstand nehmen. Man verliert nichts, wenn man nach wie vor $laenge schreibt.

In Perl 5.8 geht die Unterstützung von Unicode sehr viel weiter, unter anderem kann man nun reguläre Ausdrücke in Unicode formulieren. In den Perl-Kern integriert ist das Modul Encode, mit dem ein Perl-Programmierer ohne großen Aufwand Konversionen zwischen verschiedenen Zeichensätzen vornehmen kann:

use Encode 'from_to';
from_to($data, "iso-8859-1", "utf-8"); # vom lokalen Zeichensatz in UTF-8

In Arbeit ist Perl 6, das komplett neu entworfen und geschrieben wird und all das beinhalten wird, was die Perl-Gemeinschaft in den letzten 12 Jahren gelernt hat. Diese Version wird noch stärker auf Unicode aufgebaut sein (und wohl Grund genug für eine neue Auflage dieses Buchs sein). Es ist also sicherlich lohnenswert, sich auf dem laufenden zu halten.

Konversion zwischen Codierungssystemen

Wenn Sie eine ältere Perl-Version als 5.8 einsetzen, benötigen Sie beim Übergang von einem Zeichensatz auf einen anderen etwas Hilfe. Aber auch hier bietet unser Werkzeugkasten das eine oder andere Hilfsmittel.

iconv und Text::Iconv

iconv ist eine Bibliothek und ein Programm, das unter Windows und Unix (einschließlich Mac OS X) zur Verfügung steht. Es bietet eine einfache Schnittstelle zur Konversion eines Dokuments vom Typ A in eines vom Typ B. Von der Unix-Kommandozeile kann man es wie folgt aufrufen:

 $ iconv -f latin1 -t utf8 my_file.txt > my_unicode_file.txt 

Hat man iconv auf seinem System installiert, kann man auch das CPAN-Modul Text::Iconv installieren, das eine Perl-Schnittstelle auf diese Bibliothek anbietet. Konvertiert werden dann im Hauptspeicher stehende Strings.

Unicode::String

Eine portablere Lösung bietet das Modul Unicode::String, das auf keiner externen C-Bibliothek basiert. Die Schnittstelle des Moduls ist so einfach, wie es alle Modulschnittstellen sein sollten. Sie haben einen String? Dann stecken Sie ihn doch einfach in den Konstruktor der Klasse, um ein Objekt zu erhalten, das Ihren String enthält. Mit einer Reihe von Methoden kann man den String dann in verschiedene andere Codierungssysteme umwandeln oder auf andere, amüsante Art und Weise mißhandeln. Das folgende Beispiel testet dieses Modul.

Beispiel: Test von Unicode::String

use Unicode::String;

my $string = "Diesen Satz gibt es in ASCII und UTF-8, aber nicht in UTF-16. Mist!\n";
my $u = Unicode::String->new($string);

# $u enthält nun ein Objekt, das einen String aus 16-Bit-Zeichen repräsentiert

# Dieses Objekt kennt Overloading, die Stringoperatoren funktionieren also wie erwartet!
$u .= "\n\nHey, auf einmal ist es Unicode. Hurra!!\n";

# Ausgabe in UTF-16 (auch als UCS2 bekannt)
print $u->ucs2;

# Ausgabe etwas lesefreundlicher
print $u->utf8;

Die vielen Methoden des Moduls ermöglichen es auch, einen String in einen einfacheren Zeichensatz zu konvertieren (bei möglichem Verlust von Zeichen, die im Zielzeichensatz fehlen). Insbesondere kann man mit der Methode utf7 das achte Bit eines UTF-8-Zeichens abschneiden. Das mag ganz hilfreich sein, wenn Sie jemanden mit ASCII-Zeichen bombardieren, der bei einem Nicht-ASCII-Zeichen die Kettensäge herausholen würde.

Achtung! XML::Parser scheint gelegentlich etwas versessen auf Unicode zu sein. Egal wie man die Codierung eines Dokuments deklariert, Zeichen mit höheren Codierungspunkten werden stillschweigend in UTF-8 transformiert. Wenn Sie diese Textdaten vom Parser anfordern, bekommen Sie sie in UTF-8 zu sehen. Diese heimliche Transformation kann eine unangenehme Überraschung sein. Aus diesem Grund ist man mit XML::Parser gelegentlich auf die in diesem Abschnitt vorgestellten Transformationshilfen angewiesen.

Anordnung von Bytes

Wenn Sie ein XML-Dokument aus einer unbekannten Quelle beziehen, ist das verwendete Codierungssystem zunächst unklar. In diesem Fall kann es helfen, die Byte-Order-Markierung (BOM) zu Beginn des Dokuments zu überprüfen. In UTF-16 oder UTF-32 verfaßte Dokumente sind von einer bestimmten Reihenfolge abhängig. UTF-8 hat dieses Problem nicht. Wenn man nicht weiß, ob erst das mehr oder das weniger signifikante Byte kommt, dann ähnelt das Lesen eines solchen Dokuments unter Umständen dem Blick in einen Spiegel: Alles ist verkehrt herum.

Aus diesem Grund definiert Unicode einen speziellen Codierungspunkt, U+FEFF, als die sogenannte Byte-Order-Markierung. Die Unicode-Spezifikation sieht vor, daß in UTF-16 oder UTF-32 verfaßte Dokumente mit diesem Codierungspunkt beginnen dürfen.

UTF-8 hat seine eigene Byte-Order-Markierung, aber deren einziger Zweck ist die Identifikation des Codierungssystems. In der XML-Welt ist das relativ überflüssig. Das Codierungssystem UTF-8 hat kein Problem mit der Reihenfolge von Bytes, da UTF-8 auf Bytefolgen basiert, bei denen die Reihenfolge der einzelnen Bytes feststeht. Bei den 16- und 32-Bit-Zahlenfolgen von UTF-8 und UTF-16 ist zwar die Reihenfolge der Zahlen festgelegt, aber nicht die Reihenfolge der Bytes innerhalb einer solchen Zahl.

Ein Programm liest aus diesem Grund zunächst die ersten zwei Bytes. Wenn es dabei 0xFE und 0xFF findet, schaltet es auf UTF-16 mit Big-Endian um. Wird 0xFF 0xFE gefunden, dann wird auf Little-Endian umgeschaltet, denn einen Codierungspunkt U+FFFE gibt es in Unicode bewußt nicht. (Die BOMs von UTF-32 sind 0x00 0x00 0xFE 0xFF bzw. 0xFF 0xFE 0x00 0x00 und werden analog behandelt.)

Die XML-Spezifikation verlangt, daß UTF-16- bzw. UTF-32-codierte Dokumente eine BOM enthalten müssen. Nach der Unicode-Spezifikation folgt daraus, daß Dokumente die Netzwerkreihenfolge einhalten müssen, wenn sie von vernünftigen und wohlwollenden Autoren erzeugt wurden. Mit anderen Worten, sie müssen die Big-Endian-Variante einhalten, da diese vor langer Zeit für die Kommunikation zwischen Maschinen festgelegt wurde. Umgekehrt sollten Sie stets diese Netzwerkreihenfolge einhalten, da Sie ja vernünftig und wohlwollend sind. Das gilt zumindest dann, wenn Sie sich nicht anderweitig sicher sind. Im Zweifelsfall wird Ihnen vielleicht der folgende Quelltext helfen:

open XML_FILE, $filename or die "Kann die Datei $filename nicht öffnen: $!";
my $bom; # Hier speichern wir eine evtl. Byte-Order-Markierung ab

# Lesen der ersten zwei Bytes
read XML_FILE, $bom, 2;

# Über die Perl-Funktion ord() erhalten wir die numerischen Werte
my $ord1 = ord(substr($bom,0,1));
my $ord2 = ord(substr($bom,1,1));

if ($ord1 == 0xFE && $ord2 == 0xFF) {
  # Dies scheint ein Dokument in UTF-16, Big-Endian zu sein!
  # ... wir verhalten uns entsprechend ...
} elsif ($ord1 == 0xFF && $ord2 == 0xEF) {
  # Oh, jemand war so unfreundlich, ein Dokument in UTF-16 Little-Endian zu schicken.
  # Wir müssen zum Beispiel die Bytes vertauschen.
} else {
  # Keine Byte-Order-Markierung.
}

Sie könnten dieses Beispiel als eine Art Notlösung verwenden, wenn sich Ihr Parser beklagt, er könne kein XML-Dokument finden. Unter Umständen ist die erste Zeile tatsächlich eine korrekte <?xml ... >-Deklaration, die Ihr Parser aber einfach nicht erkennt.

  

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Perl & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2003 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Perl & XML" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de