22 Jun 2008
by Nickin Basics
Als Webentwickler, speziell als deutschsprachiger Webentwickler wird man fast täglich mit Fragen der Codierung konfrontiert: Immer wieder kommen statt Umlauten und Eszett kryptische Zeichen auf Webseiten, xml-Dateien werden nicht korrekt interpretiert, da sie mit “” beginnen oder aber Betreffzeilen von generierten E-Mails werden in Outlook als “????? ??? ??????? ???? ?” dargestellt.
Viele Entwickler wissen aber nicht so genau, was es mit Zeichensätzen, Codierungen und Unicode auf sich hat und hoffen einfach, daß das Problem irgendwie von selbst verschwindet. Was es aber nicht tut.
Dabei ist die Lösung ganz einfach: Wer einmal verstanden hat, was Zeichen, Zeichensätze, Zeichenkodierung und Unicode ist, der kann Fehler sehr einfach vermeiden. In diesem Artikel fasse ich das absolute Minimum zusammen, was ein Webentwickler wissen sollte.
Am einfachsten geht man das Thema chronologisch an:
Es begab sich also in etwa zu der Zeit als Unix erfunden wurde: Die einzigen Zeichen, die Programmierer interessierten waren englische Buchstaben und Zahlen und sie hatten einen Code für diese Zeichen, der ASCII hieß und jedes Zeichen durch eine Zahl zwischen 32 und 127 darstellte. Die Leertaste war 32, N war 78 usw.
Die Zahlen von 0 bis 127 ließen sich mit 7 Bit darstellen, aber die meisten Computer dieser Tage benutzten 8-bit Bytes, also konnte man nicht nur jedes ASCII-Zeichen speichern, man hatte noch ein komplette Bit und somit 128 zusätzliche Zeichen, die man für seine eigenen Zwecke gebrauchen konnte. Zeichen unter 32 waren sogenannte “Steuerzeichen” mit so wichtigen Aufgaben wie den Computer piepsen zu lassen (Code 7) oder eine Seite aus dem Drucker zu werfen und eine neue einzuziehen (Code 12).
Und die Welt war in Ordnung, solange man nur Englisch sprach.
Weil ein Byte acht Bit speichert, kamen viele Menschen auf die tolle Idee, die Codes 128 bis 255 für spezielle Dinge zu verwenden. Apple brauchte natürlich ein kleines Apfelzeichen, IBM hatte in seinem OEM-Zeichensatz einige Sonderzeichen mit Akzenten und Pünktchen für uns Europäer und fantastische Linien aus denen man tolle Rahmen bauen konnte (Windows war noch lange nicht erfunden). Das Problem war nur, das jeder sein eigenes Süppchen kochte und wenn man auf einem Apple II in einem Text “Äpfel” schrieb wurde das auf einem IBM-PC als “Çpfel” angezeigt. Noch schlimmer war es mit Russisch, wo keine Einigkeit herrschte, wie man die oberen 128 Zeichen verwenden sollte und man nicht einmal russische Dokumente zuverlässig untereinander austauschen konnte.
So wurde der ANSI Standard festgeschrieben, in dem man sich einigte, was unterhalb von Code 128 passieren sollte, aber viele Möglichkeiten vorsah, was mit Code 128 und aufwärts passieren konnte, abhängig davon wo man lebte. Diese Möglichkeiten nannte man “code pages”. In Deutschland verwendete man die Codepage 850, in Griechenland 869, usw. IBM hatte sogar einige als “Multilingual” gekennzeichnete Codepages, mit denen man mehrere Sprachen auf einem Computer verwenden konnte. Wow!
Wollte man aber gleichzeitig griechische Zeichen und deutsche Umlaute auf einem Computer dann war das unmöglich, es sei denn man nutzte spezielle Programme, die diese Zeichen als Bitmap-Schriften darstellten, denn Umlaute und das griechische Alphabet benutzten unterschiedliche code pages.
In Asien hatte man Alphabete mit mehreren tausend Buchstaben, die sich in 8 Bit einfach nicht unterbringen ließen. Also Benutzte man ein System namens DBCS (“Double Byte Character Set”), in dem einige Zeichen in 8 Bit gespeichert wurden, andere in 16 Bit. (Ziemlich schwierig, eine Funktion zu schreiben die sich durch einen so codierten String rückwärts bewegt…)
Trotzdem taten die meisten Programmierer so, als wäre ein Byte ein Zeichen und ein Zeichen acht Bit und solange man keine Zeichenketten von einem Computer zu einem anderen schickte (oder mehr als eine Sprache sprach), funktionierte alles irgendwie. Bis dann das Internet kam, und man auf einmal ständig Zeichenketten von Computer zu Computer schickte. Zum Glück war Unicode schon erfunden.
Unicode
Wenn Sie bis hier und gelesen haben und jetzt denken: “Unicode, das sind jetzt einfach 16 Bit pro Zeichen. Das habe ich doch schon gewusst…“ muss ich leider sagen: Weiterlesen, denn das ist so leider nicht korrekt. Das ist einfach nur ein weit verbreiteter Mythos über Unicode.
Unicode ist der Versuch, einen Zeichensatz zu erschaffen, der eine Obermenge von jedem anderen Zeichensystem dieses Planeten (und zusätzlich auch Klingonisch) darstellt. Unicode hat aber auch ein anderes Konzept für Zeichen. Bis jetzt sind wir immer davon ausgegangen, das ein Zeichen einigen Bits im Speicher oder auf der Festplatte zugeordnet wird:
N -> 0100 1110
Man muss verstehen, wie Unicode über Zeichen denkt oder man begreift das Konzept hinter Unicode nicht. In Unicode wird ein Buchstabe auf einen „code point“ abgebildet, der immer noch ein theoretisches Konzept ist. Wie dieser „code point“ im Speicher oder auf der Festplatte abgebildet wird ist eine völlig andere Geschichte.
In Unicode ist der Buchstabe N zunächst nur eine Vorstellung von einem Buchstaben. Diese Vorstellung von N ist anders als die Vorstellung von K, oder von n, aber die gleiche wie N oder N. Die Idee, das N in Times New Roman, Fett das gleiche ist wie N in Helvetica, kursiv, aber anders als „n“ klein geschrieben scheint nur auf den ersten Blick zwingend. Aber ist “ß” ein echter Buchstabe oder nur eine andere Art, “ss” zu schreiben? Immerhin wird “ß” in Versalien zu “ss”.
Wenn sich die Form eines Buchstabens ändert, weil er am Ende eines Wortes steht, ist das dann ein anderer Buchstabe? Im Hebräischen ja, im Arabischen nicht. Das tolle ist: Die klugen Leute im Unicode-Konsortium haben alle diese Dinge schon herausgefunden, und wir müssen und diese Gedanken nicht mehr machen! YAY!
Jeder Vorstellung eines Buchstabens in einem Alphabet ist vom Unicode Konsortium eine Nummer zugeordnet worden, die sich so schreibt: U+05D0. Diese Nummer wird “code point” genannt. Das U+ bedeutet “Unicode” und die vier Zahlen sind Hexadezimal. U+05D0 ist das Zeichen für den hebräischen Buchstaben Alef. Der Buchstabe N wäre U+004E. Sucht man den Code für ein bestimmtes Zeichen hilft die Zeichensatztabelle von Windows weiter.
Es gibt keine Übergrenze für die Anzahl von Buchstaben, die Unicode beschreibt und es gibt in der Tat sehr viel mehr als die 65536 Zeichen, die sich in zwei Byte pressen lassen. Soviel zu diesem Mythos.
Nehmen wir die Zeichenkette “Nick”. Diese Entspricht in Unicode den vier “code pointers”:
U+004E U+0069 U+0063 U+006B.
Und nicht mehr. Wie diese im Speicher abgelegt werden oder in eine E-Mail gepresst werden können ist bisher noch nicht gesagt. Hier kommen Encodings ins Spiel.
Encodings
Die erste und einfachste Idee, Unicode zu speichern führte zu dem erwähnten “zwei Byte” Mythos. Irgendeiner sagte: “Hey, ich speichere die code pointer einfach in zwei Bytes ab!” Und “Nick” wurde zu:
00 4E 00 69 00 63 00 6B
Einige CPUs konnten aber schneller mit einer anderen Darstellung arbeiten:
4E 00 69 00 63 00 6B 00
Was technisch gesehen das gleiche beschreibt und in der Tat wollten die ersten Implementierungen von Unicode dies beides ermöglichen: Big Endian und Low Endian. Und schon gab es zwei Möglichkeiten Unicode zu speichern. Also führte man eine Konvention ein: Man fügte am Anfang jeder Unicode-Zeichenkette „FE FF“ ein. Das ganze nannte man “Unicode Byte Order Mark” oder kurz “BOM”. Wenn man das hohe und das niedrige Byte vertauschen will schreibt man FF FE und jeder weiß, dass man jedes zweite Byte mit dem vorhergehenden tauschen muss. Aber nicht jede Unicode Zeichenkette muss einen BOM-Header haben.
Eine Zeit lang war das gut genug, aber dann dachten einige amerikanische Programmierer, die eigentlich nie Zeichen über U+00FF benutzten: “Hey. Das sind ziemlich viele Nullen.” Verrückte Amis: Fahren riesige SUVs, die 20 Liter Sprit auf 100 Kilometer benötigen, aber wegen so ein paar Nullen wollen Sie auf einmal sparen! Aber diese Amerikaner wollten einfach nicht doppelt so viel Speicherplatz verbraten, um Zeichenketten zu speichern. Außerdem hatten Sie noch wahnsinnig viele Dateien, die in ASCII oder irgendwelchen Codepages gespeichert waren, und wer sollte die alle konvertieren? Aus diesem Grund ignorierten Sie Unicode über Jahre, was die Probleme natürlich nicht löste, sondern verschlimmerte.
Also wurde UTF-8 erfunden. UTF-8 ist zunächst mal nichts anderes als eine Möglichkeit, die U+ Nummern in einem Speicher, der Bytes mit 8 Bit benutzt zu speichern. Aber jeder “code point” von 0 bis 127 wird in nur einem Byte gespeichert. Lediglich code points über 127 werden in 2, 3, oder bis zu 4 Bytes gespeichert.
Das schöne daran: Englischer Text sieht genauso aus wie in ASCII. Die Amis bemerken nicht mal, das etwas anders ist! “Nick” würde 4E 69 63 6B gespeichert werden, also genauso wie in ASCII, ANSI, oder jedem OEM-Zeichensatz. Nur wenn man Umlaute, griechische Buchstaben oder Klingonisch verwendet, muss man mehrere Bytes zum Speichern verwenden. Sogar ein einfaches Null-Byte als String-Terminator bleibt in UTF-8 intakt!
Das waren bisher schon drei Möglichkeiten, Unicode zu kodieren: Die erste Möglichkeit, Zeichen als 2 Byte zu speichern heißt UCS-2 (weil sie zwei Bytes benutzt) oder auch UTF-16 (wegen der 16 Bit) und man muss weiterhin herausfinden ob es “UCS-2 Big Endian” oder “UCS-2 Little Endian” ist. Außerdem haben wir noch UTF-8, das auch mit alten Programmen funktioniert, die keine Zeichen außer ASCII kennen.
Ein weiteres Encoding ist UTF-7, in dem das erste Bit immer Null ist, so dass es auch auf Systemen funktioniert, die nur 7 Bit zur Darstellung von Zeichen verwenden. Weiterhin ist auch noch USC-4 erwähnenswert, das immer 4 Byte zur Darstellung eines Zeichens benutzt, aber so verschwenderisch ist eigentlich fast niemand.
Natürlich kann man Unicode “code points” auch in einen traditionellen Zeichensatz wie ASCII, oder Hebräisches ANSI kodieren. Nun ja, fast – wenn es kein passendes Zeichen für den “code point” eines Zeichens gibt erhält man ein Fragezeichen. Oder ein Rechteck. Oder ein auf die Spitze gestelltes schwarzes Rechteck mit einem weißen Fragezeichen darin.
Wenn man einen deutschen Unicode-Text in ISO-8859-1 Encoding (Latin-1, nützlich für alle Westeuropäischen Sprachen) kodiert, bleiben alle Umlaute und ß erhalten. Kodiert man einen russischen Unicode-Text im gleichen Encoding, wird man eine Menge “?” vorfinden, da es keine Entsprechungen für kyrillische Buchstaben in diesem Encoding gibt, wohingegen Encodings wie UTF-7, 8, 16 oder 32 für alle Buchstaben eine Entsprechung haben.
Das wirklich Wichtige zu Encodings:
Wenn Sie alles vergessen, was ich hier geschrieben habe, merken Sie sich bitte wenigstens eines: Es macht keinen Sinn, eine Zeichenkette zu haben ohne zu wissen, wie sie kodiert ist. Vor allem darf man nicht annehmen, das sie aus ASCII-Zeichen besteht und man sich um nichts kümmern muss, denn:
Es gibt keinen uncodierten Text!
Wenn man eine Zeichenkette hat, im Speicher, auf einer Webseite oder in einer E-Mail muss man auch ihr Encoding kennen, um Sie dem Benutzer richtig anzuzeigen.
Alle Encoding Fehler sind darauf zurückzuführen, das an irgend einem Punkt ein Programmierer nicht gewusst hat, in was für einem Encoding eine Zeichenkette vorlag. Es gibt hunderte von Encodings, und oberhalb von 127 hat man keine Ahnung, welches Zeichen gemeint ist.
Und wie können wir mitteilen, was für ein Encoding benutzt wird? Ganz einfach: In E-Mails schreiben wir das Encoding in den Header in der Form:
Content-Type: text/plain; charset="UTF-8"
Fertig. Das war jetzt nicht so schwer, oder?
Für Webseiten gab es eine ähnliche Idee. Der Webserver sollte das Encoding als Teil des http-headers mit der Seite ausliefern – also nicht im eigentlichen HTML.
Auf großen Webservern, die Seiten von unterschiedlichen Benutzern in unterschiedlichen Sprachen bereitstellen ist dies aber kaum möglich – wie soll der Server wissen, in welchem Encoding der Benutzer die Seiten hochgeladen hat? Also kann der Server auch keinen passenden Content-Type Header schicken.
Also musste man das Encoding in die HTML-Datei selbst schreiben, was jetzt alle Puristen aufschreien lässt: “Wie kann man eine Datei lesen, wenn man nicht weiß, in welchem Encoding sie vorliegt?!” Nun ja: Fast jedes Encoding macht mit den Zeichen von 32 bis 127 das gleiche, also sollte man zumindest so weit kommen, ohne komische Zeichen verwenden zu müssen:
<html>
<head>
<meta http-equiv=“Content-Type” content=“text/html; charset=utf-8″>
Wichtig ist: Der Meta-Tag muss der erste Tag im Bereich sein, denn sobald der Browser auf diesen Tag stößt wird die Datei neu im angegebenen Encoding erneut interpretiert.
Was aber, wenn der Browser weder im http header noch im meta-tag eine Angabe zum Encoding findet? Nun ja, der Internet Explorer macht da etwas ganz Merkwürdiges: Er versucht, die Sprache und das Encoding aufgrund der Häufigkeit von bestimmten Bytemustern in Texten zu erraten. Erstaunlicherweise funktioniert das dann meistens auch noch recht gut, manchmal bekommt man allerdings auch seine deutsche Webseite im Encoding “Thai (Windows-874)” angezeigt, weil die Häufigkeit der Bytemuster einfach besser dazu gepasst hat.
Das war ein sehr langer erster Artikel, aber wer mir bis hier hin gefolgt ist sollte jetzt ein Minimum über Zeichensätze und Encodings wissen und die meisten Fehler aus diesem Bereich vermeiden können.