Einführung in Charsets und Encodings

18. Mai 2009 um 19:54 Uhr von Wolfgang Stengel zu XML, Programmierung, PHP, HTML
Da ich in letzer Zeit immer wieder Missverständliches zu dem Thema gehört und gelesen, oder Leute Dinge sagen hören habe wie "Uh jetzt hat's mir hier die Umlaute zerhauen, warum hab' ich hier ein A mit einer Tilde drüber?", schreibe ich heute mal ein wenig was zu Charsets und Encodings auf.

Zuerst zwei wichtige Begriffserklärungen, die oft verwechselt oder falsch interpretiert werden.

Grundlagen

Charset: Ein Charset (Zeichensatz) ist eine Liste von Zeichen die zu einer Gruppe zusammengefasst werden, zum Beispiel ASCII, ISO-8859-1 oder Unicode. Dabei wird in der jedem Zeichen eine Nummer zugeordnet (zum Beispiel ist im ASCII-Zeichensatz dem Zeichen A die 65 zugeordnet). Das Charset an sich hat erstmal mit der digitalen Bearbeitung oder mit der Darstellung der Zeichen nichts zu tun.

Encoding: Ein Encoding wird dazu verwendet Zeichen digital zu speichern. Im einfachsten Fall werden ASCII-Zeichen direkt als Bytes im Hauptspeicher oder auf der Festplatte gespeichert (die Zahl 65 des Zeichens A kann direkt so abgelegt werden, da die Nummern der ASCII-Zeichen nie die Bytegrenze von 255 überschreiten). In komplizierteren Fällen (wie zum Beispiel bei Unicode) müssen einzelne Zeichen auf mehrere Bytes aufgeteilt werden, da die zugeordneten Nummern größer als 255 werden können (das gängigste Encoding für Unicode ist UTF-8).

ASCII

Der American Standard Code for Information Interchange ist das einfachste aller Charsets. Er wird von allen gängigen Computern, Betriebssystemen und allen Programmen unterstützt und richtig dargestellt. Damit enden auch schon die Vorteile, denn es sind außer Satzzeichen, Ziffern und den lateinischen Buchstaben A-Z und a-z nicht sehr viele weitere Zeichen in ASCII enthalten (zum Beispiel keine deutschen Umlaute). Die Zeichen sind von 0 bis 127 durchnummeriert und können so als Bytes direkt und ohne besonderes Encoding im Speicher abgelegt werden.).

8-Bit-Charsets

Da man mit diesen 128 Zeichen natürlich nicht weit kommt, gibt es "größere" genormte 8-Bit-Charsets. Alle diese 8-Bit-Charsets entsprechen in der Zuordnung von 0-127 exakt dem ASCII-Charset. Die restlichen Zeichen mit den Nummern 128-255 (die "obere Hälfte") enthalten oft regionsspezifische Zeichen. ISO-8859-1 (auch als Latin 1 bezeichnet) enthält dort unter Anderem die deutschen und französischen Sonderzeichen, ISO-8859-2 zum Beispiel die polnischen und ungarischen Sonderzeichen. Ein weiteres gängiges Charset dieser Kategorie ist Windows-1252, welches auf ISO-8859-1 basiert, aber noch ein paar weitere Zeichen enthält (zum Beispiel das Euro-Symbol, den Gedankenstrich oder die geschwungenen Anführungszeichen, kurz gesagt all die Zeichen die man schon mal von Usern aus einem Word-Dokument in eine Textarea kopiert bekommt). Wird eines dieser Charsets im HTML-Header über den Meta-Angabe http-equiv="content-type" angegeben, interpretiert der Browser Zeichen mit zugeordneten Nummern > 127 als Zeichen dieses Charsets und stellt sie richtig dar.

Diese 8-Bit-Zeichensätze eignen sich für Anwendungen, bei denen auf jeweils einer Seite nur eine Sprache verwendet wird. Der Vorteil ist das einfache Encoding, bei dem wie bei ASCII kann der Zahlenwert des Zeichens einfach 1:1 als Byte abgelegt werden. Der Nachteil ist die Einschränkung auf die entsprechende Region. Zum Beispiel können mit alleine mit ISO-8859-2 auf einer Webseite nur einige der osteuropäischen Sprachen dargestellt werden, nicht gleichzeitig auch westeuropäische, also in etwa deutsch.

PHP verwendet intern ISO-8859-1, daher beziehen sich Funktionen wie zum Beispiel utf8_encode() implizit auf die Umwandlung von ISO-8859-1 nach Unicode mit dem Encoding UTF-8. Umgekehrt kann utf8_decode() mit deutschen Umlauten, die UFT8-encodet sind, umgehen, polnische Sonderzeichen sind jedoch in ISO-8859-1 nicht darstellbar und erzeugen daher Fragezeichen. In PHP6 wird es hier dann andere Möglichkeiten geben.

Unicode

Um mehrere grundsätzlich verschiedene Sprachen auf einer Seite darzustellen (z.B. in einem Backend welches Aufträge aus unterschiedlichen Ländern in einer Liste darstellt) wird Unicode benötigt. Bei Unicode wird nahezu jedem Zeichen, das auf diesem Planeten existiert, eine Nummer eindeutig zugeordnet. Diese Zuordnung wird von allen modernen Betriebssystemen und Programmen verstanden und können von den Standardfonts wie Arial und Helvetica auch dargestellt werden.

Wie bei den 8-Bit-Zeichensätzen entsprechen die Nummern des ASCII-Codes auch im Unicode denselben Zeichen (also entspricht die Nummer 65 auch im Unicode dem Zeichen A). Die deutschen Umlaute, die polnischen oder gar chinesische Zeichen haben größere Werte, zum Beispiel wird das Ä der Zahl 196 zugeordnet, das kleine durchgestrichene L entspricht 322. Damit kommen wir zum Hauptnachteil von Unicode: Ein Byte-Encoding wie bei ASCII kommt hier nicht mehr in Frage.

UTF-8

Das gängigste Encoding für Unicode ist UTF-8. Da die den Zeichen zugeordneten Nummern theoretisch bis 1.114.112 gehen können, werden hierbei einzelne Zeichen auf bis zu vier Bytes aufgeteilt. Dabei werden so wenige Bytes wie möglich verwendet. ASCII-Zeichen werden als einzelne Bytes mit ihren originalen ASCII-Werten dargestellt, daher wird ein einfaches "Hallo" in ASCII und in UTF-8 gleich aussehen. Bei nicht-ASCII-Zeichen werden zwei oder mehr Bytes verwendet, so werden aus den UTF-8-Bytes von "Häuser" in ISO-8859-1 "Häuser" und aus "Materiały" wird "MateriaÅ‚y" (in der normalerweise sinnlosen direkten 1:1-Darstellung der Bytes). Das Ergebnis ist ein handlicher Bytestrom von 8-Bit-Zeichen, der mit allen Arten von aktueller Computertechnologie leicht verarbeitet werden kann. Wird im HTML-Header UTF-8 als Encoding angegeben, werden Zeichen mit zugeordneten Zahlen > 127 als UTF-8 interpretiert und dargestellt. Außerdem kommt UTF-8 oft in XML-Dateien zur Verwendung.

Die Hauptnachteile sind die Tatsachen, dass diese Zeichen in Reinform schwer zu lesen und unmöglich zu tippen sind. Außerdem funktionieren die String-Funktionen in PHP < 6 nicht mit UTF-8, da einzelne Zeichen sich auf mehrere Bytes erstrecken können, was von PHP nicht verstanden wird.

Unicode in HTML

Eine einfachere Methode um Unicode zu speichern sind numerische HTML-Entities. Dabei wird der dem Unicode-Zeichen zugeordnete Wert mit vorgestelltem Ampersand und Doppelkreuz encodet. Ein Ä wird in HTML zu &#196; und das ł zu &#322;. Eine hexadezimale Angabe ist auch möglich (zum Beispiel &#x1EA; für Ǫ). Die so encodeten Zeichen werden dann unabhängig von dem im HTML-Header gewählten Charset erkannt und dargestellt. Außerdem lassen sie sich sehr leicht von Hand eingeben, obwohl sie natürlich in einer Tabelle nachgeschlagen werden müssen. Diese Methode ist im Prinzip kein Encoding, da der resultierende Text ASCII-konform ist und als ASCII gespeichert wird. Dies sorgt regelmässig für Verwirrung, da man HTML-Entities auch mit UTF-8 oder ISO-8859-2 mischen kann, aber meiner Meinung nach nicht mischen sollte.

PHP

Wie gesagt verwendet PHP intern ISO-8859-1, stellt aber ab PHP 4.0.6 eine Funktion mb_convert_encoding() zur Verfügung. Zusammen mit htmlentities() oder einer Entity-Liste und ein paar regulären Ausdrücken lassen sich die einzelnen Encodings und Charsets beliebig ineinander umwandeln.

XML

In XML-Text wird der Zeichensatz üblicherweise im ersten -Tag definiert, etwa mit für UTF-8. Die Text-Nodes müssen dann also in diesem Beispiel in UTF-8 vorliegen. Jedoch müssen nicht nur Zeichen außerhalb des ASCII-Bereiches möglicherweise umgewandelt werden, sondern auch die Zeichen <, > und &, da diese Zeichen spezielle Bedeutungen innerhalb von XML haben. Folgende Zeile in PHP schützt einen Text-Node vor dieser Bedeutung:

$text=str_replace('<','&lt;',str_replace('>','&gt;',str_replace('&','&amp;',$text))));

Epilog

Eigentlich garnicht so dramatisch. Ich persönlich verwende inzwischen wann immer es geht UTF-8 bei allen Datenquellen. Damit kommt man fast überall durch und muss nichts konvertieren. Auch User-Eingaben werden vom Browser als UTF-8 geliefert wenn der entsprechende Content-Type und/oder die Meta-Angabe verwendet wird. Eine gute Referenz von Glyphen und der Charsets in denen sie vorkommen bietet die Letter Database des Institute of the Estonian Language. Meiner Meinung nach ist das Wichtigste beim Umgang mit Zeichen, dass man sich von vorneherein bewusst macht in welchem Encoding und in welchem Charset Daten vorliegen, wenn sie gespeichert, verarbeitet oder angezeigt werden sollen.
PHP Wolfgang Stengel