Hast du dich nicht schon mal gefragt, was es mit dieser ominösen Marke "Content-Type" auf sich hat? Du weißt schon, dieses Teil, das du in den HTML-Kode einfügen sollst, von dem du aber nicht weißt, was das eigentlich sein soll.
Hat dich schon mal Elektropost von einem Freundes aus Bulgarien erreicht mit dem Betreff ""???? ?????? ??? ????"?
Ich war entsetzt, als ich entdeckte, wie viele Entwickler nicht auf der Höhe der Zeit sind, wenn es um die Mysterien von Zeichensätzen, Kodierung, Unicode, usw. geht. Vor ein paar Jahren fragte ein Beta-Tester, ob FogBUGZ wohl auch mit japanischer E-Mail zurechtkäme. Japanisch? Wie, ihr kriegt japanische E-Mails? Ich konnte dazu nichts sagen. Nachdem ich mir das gekaufte ActiveX-Bedienelement genauer angeschaut hatte - wir nutzten das, um Nachrichten im MIME-Format zu zerlegen -, entdeckten wir, das dieses Ding genau das Falsche mit den Zeichensätzen machte. Wir mussten also Kode schreiben, um die falsche Konvertierung rückgängig zu machen und danach richtig zu konvertieren. Ich hab' mir dann noch eine weitere kommerzielle Programmbibliothek angesehen, und auch dort wurden die Zeichensätze völlig falsch behandelt. Mit dem Entwickler dieses Pakets habe ich korrespondiert - er vermutete, dass "sich da wohl nichts machen lässt". Wie viele andere Programmierer hoffte er, das würde sich irgendwie von selbst erledigen.
Wird es aber nicht! Als ich dann auch noch herausfand, dass PHP, die beliebte Sprache für Internetanwendungen, von Kodierungsproblemen völlig unbeleckt ist (Es ist froh und glücklich mit 8 Bit pro Zeichen und macht es damit so gut wie unmöglich, eine inernationale Anwendung zu entwickeln.), da dachte ich mir, genug ist genug!
Darum habe ich eine Erklärung abzugeben: Wenn du im Jahr 2003 als Programmierer arbeitest und die Grundlagen von Zeichen, Zeichensätzen, Kodierungen und Unicode nicht verstehst, und wenn ich dich erwische: ich steck' dich in ein U-Boot und lass' dich 6 Monate Zwiebeln schälen. Verlass dich drauf!
Und noch etwas:
SO SCHWER IST ES GAR NICHT
In diesem Aufsatz werde ich dir genau das nahebringen, was jeder aktive Programmierer wissen sollte. Dieses Gerede von "reiner Text = ASCII = Zeichen haben 8 Bit" ist nicht einfach nur falsch, es ist hoffnungslos verkehrt. Wenn du noch immer so programmierst, bist du wie ein Arzt, der nicht an Bakterien glaubt. Bitte schreib' keine weitere Zeile Kode, bevor du nicht diesen Aufsatz zu Ende gelesen hast.
Bevor ich anfange, will ich noch warnen: Falls du eine von den seltenen Ausnahmen bist, die sich mit Internationalisierung auskennen, wirst du meine Abhandlung für zu stark vereinfachend halten. Ich versuche hier nur die untere Latte einzuziehen, so dass jeder verstehen kann, um was es geht, und ein jeder Kode schreiben kann, für den die Hoffnung besteht, dass er auch mit anderem als englischsprachigem Text umgehen kann, wo noch nicht mal Buchstaben mit Akzent möglich sind. Und ich sollte noch darauf hinweisen, dass die Handhabung von Zeichen nur einen kleinen Teil dessen ausmacht, was Software international brauchbar werden lässt. Ich kann aber nur ein Thema gleichzeitig behandeln, und heute heißt es "Zeichensätze".
Am einfachsten wird es sein, ich gehe chronologisch vor.
Wahrscheinlich meinst du, ich fange jetzt mit den uralten Zeichensätzen wie EBCDIC an. Nein, tu ich nicht. EBCDIC ist nicht lebensnotwendig - sooo weit müssen wir nicht zurückgehen.
Damals, in der guten, alten Zeit, als Unix gerade erfunden wurde und K&R "The C Programming Language" schrieben, war alles noch ganz einfach. Die einzigen Buchstaben von Bedeutung waren die guten, alten englischen ohne Akzente, und unsere Kodierung nannte sich ASCII; die konnte alle Buchstaben auf Zahlen zwischen 32 und 127 abbilden. Zwischenraum war 32, der Buchstabe "A" war 65, usw. Das konnte man bequem in 7 Bit unterbringen. Die meisten der damaligen Computer benutzen 8-Bit-Bytes, so dass man nicht nur alle möglichen ASCII-Zeichen speichern konnte; man hatte sogar ein Bit übrig, das man, wenn man gemein genug war, für eigene Zwecke missbrauchen konnte: Die trüben Tassen von WordStar benutzten doch tatsächlich das hohe Bit, um den letzten Buchstaben jedes Worts zu markieren, und schränkten so WordStar auf die englische Sprache ein. Kodierungen unter 32 wurden nicht druckbare Zeichen genannt und zum fluchen benutzt. Kleiner Scherz! Die wurden als Steuerzeichen verwendet, 7 brachte deinen Rechner zum Piepen, 12 katapultierte das aktuelle Blatt Papier aus dem Drucker und zog ein neues ein.
Alles war gut, solange deine Sprache die englische war.
Weil ein Byte Platz für 8 Bit hat, dachten sich eine Menge Leute "Super, die Nummern 128-255 können wir ja für unsere Bedürfnisse nutzen." Dummerweise hatten viele Leute, gleichzeitig verschiedene Ideen, was man dort unterbringen könnte. Der IBM-PC zum Beispiel kam mit etwas, das als OEM-Zeichensatz bekannt wurde und ein paar Akzent-Buchstaben für europäische Sprachen zur Verfügung stellte, sowie einen ganzen Vorrat von Symbolen für Strichzeichnungen: waagrechte Striche, senkrechte Striche, waagrechte Striche mit Anhängsel rechts, usw., und damit konnte man schicke Kästchen und Linien auf den Bildschirm malen; noch immer zu bewundern auf dem 8088-Computer im Waschsalon nebenan. Als die Leute auch außerhalb der USA anfingen, PCs zu kaufen, wurden ganz schnell ganz viele verschiedene OEM-Zeichensätze ausgedacht, die alle die oberen 128 Nummern für ihre eigene Zwecke nutzten. Zum Beispiel würde auf manchen PCs der Zeichenkode 130 als é angezeigt, doch auf israelischen Rechnern wäre das der hebräische Buchstabe Gimel (), so dass, wenn Amerikaner ihre Résumés nach Israel schickten, die dort als Rsums ankommen würden. In anderen Fällen wie Russisch gabs viele verschiedene Vorstellungen, wie die oberen 128 Nummern zu verwenden seien; es war also nicht mal möglich, verlässlich russische Dokumente auszutauschen.
Schließlich wurden all diese Hier-gibt-es-Freibier-OEMs im ANSI-Standard festgehalten; alle waren sich einig über den Bereich unter 128 und das war nichts anderes als ASCII, aber der Bereich darüber wurde ganz verschieden gehandhabt, je nachdem, wo man sich befand. Diese unterschiedlichen Systeme nannte man Zeichenumsetztabellen (englisch: code pages). So benutzte zum Beispiel DOS in Israel die Tabelle 862, während die Griechen mit 737 arbeiteten. Die waren unterhalb von 128 gleich, aber unterschiedlich darüber - da, wo die lustigen Buchstaben zu Hause sind. Für MS-DOS gab es dutzende nationale Versionen, von Englisch bis Isländisch, es gab sogar ein paar mehrsprachige, die Esperanto und Galizisch auf ein und denselben Rechner brachten. Boah eh!. Aber es war unmöglich, sagen wir mal, Hebräisch und Griechisch auf demselben Rechner zu nutzen (wenn man nicht ein Programm selbst schreiben wollte, dass alles mit Bitmap-Graphiken anzeigen würde.), denn Hebräisch und Griechisch interpretierten die hohen Kodierungen verschieden.
In der Zwischenzeit passierten in Asien noch viel verrücktere Sachen; man musste mit der Tatsache zurechtkommen, dass asiatische Alphabete hunderte von Buchstaben haben, die man nie und nimmer in 8 Bit unterkriegen würde. Üblicherweise nahm man dafür dieses verhaute System namnes DBCS, den 2-Byte-Zeichensatz (englisch: double byte character set), in dem einige Buchstaben in einem Byte, andere in zwei gespeichert wurden. Eine Zeichenkette vorwärts zu verarbeiten war leicht, rückwärts war's nahezu unmöglich. Man verlangte von Programmierer s++ und s-- zu vermeiden, und stattdessen Funktionen zu rufen, bei Windows z.B. AnsiNext oder AnsiPrev; die wussten dann, mit dem Durcheinander umzugehen.
Aber noch immer taten die meisten, als ob ein Byte ein Zeichen wäre, und ein Zeichen waren 8 Bit, und das ging solange gut, wie man nicht Zeichenketten von einem Rechner an einen anderen übertragen musste oder mehr als eine Sprache sprach. Natürlich, als dann das Internet über uns hereinbrach und das Übertragen von Zeichen zwischen Computern alltäglich wurde, da wurde das ganze Durcheinander sichtbar. Zum Glück war bereits Unicode erfunden worden.
Unicode war der mutige Versuch eines Zeichensatzes, der alle ernsthaften Schriftsysteme des Planeten enthielt, und darüber hinaus ein paar imaginäre wie zum Beispiel Klingonisch. Es gibt das Missverständnis, Unicode sei einfach ein 16-Bit-Kode, wo jedes Zeichen 16 Bit hat und es deshalb 65.536 mögliche Zeichen gibt. Das ist nicht richtig! Es ist das am weitesten verbreitete Gerücht über Unicode - also nicht so schlimm, wenn du das auch geglaubt hast.
In Wirklichkeit hat Unicode einen ganz anderen Denkansatz; den musst du verstanden haben, sonst macht alles weitere keinen Sinn.
Bisher sind wir davon ausgegangen, dass ein Buchstabe einigen Bits entspricht, die man auf Platte oder im Hauptspeicher ablegt.
A -> 0100 0001
In Unicode hingegen entspricht ein Buchstabe etwas, das man Kode-Nummer (englisch: code point) nennt, und das immer noch nur ein theoretische Konzept ist. Wie diese Kode-Nummer im Speicher oder auf Festplatte repräsentiert wird, ist eine ganz andere Geschichte.
In Unicode ist der Buchstabe A ein platonisches Idealbild. Es schwebt über den Wolken:
A
Dieses platonische A unterscheidet sich von B und auch von a, doch es ist dasgleiche wie A und A und A.
Die Idee, dass das A in der Schriftart Times New Roman dergleiche Buchstabe ist wie das A in der Schriftart Helvetica, ist weitgehend unstrittig, In anderen Sprachen hingegen kann es schon umstritten sein, was überhaupt ein Buchstabe ist. Ist das deutsche ß ein richtiger Buchstabe oder nur eine phantasievolle Art ss zu schreiben? Wenn sich die Form eines Buchstabens sich am Wortende ändert, ist das dann ein eigener Buchstabe? Im Hebräischen ja, im Arabischen nein. Egal. Die schlauen Leute vom Unicode-Konsortium haben das in den letzten 10 Jahren geklärt - in einer Vielzahl kontroverser Debatten. Das muss dich nicht mehr kümmern, es ist alles schon verstanden und festgeklopft.
Jedem Idealbuchstaben in jedem Alphabet wird vom Unicode-Konsortium eine magische Zahl zugewisen, die man so schreibt: U+0639. Diese magische Zahl nent man Kode-Nummer (englisch: code point). Das U+ bedeutet "Unicode" und die Nummer ist hexadezimal. U+0639 ist der arabische Buchstabe Ain. Das A ist U+0041. Alle zusammen findet man unter Windows 2000/XP mit dem Werkzeug charmap, oder auf der Unicode-Internetseite.
Es gibt keine Grenze für die Anzahl der Buchstaben, die Unicode definieren kann, und in der Tat geht es schon über 65.536 hinaus, so dass man nicht alle Unicode-Buchstaben in zwei Bytes pressen kann. Aber das war eh nur ein Gerücht.
OK, wir haben also eine Zeichenkette:
Hallo
was in Unicode diesen fünf Kode-Nummern entspricht:
U+0048 U+0061 U+006C U+006C U+006F.
Nichts als eine Handvoll Kode-Nummern, Zahlen eigentlich. Bis jetzt haben wir nämlich nichts darüber gesagt, wie die gespeichert oder in E-Mails dargestellt werden.
An dieser Stelle kommt Kodierung ins Spiel.
Die erste Idee, Unicode zu kodieren, war es, die zu dem Gerücht von den zwei Bytes führte, und die lautete - oh Wunder - speichern wir doch diese Kode-Nummern in zwei Bytes. So wird aus Hallo:
00 48 00 61 00 6C 00 6C 00 6F
Richtig? ... Halt, langsam! Könnte es nicht genausogut heißen:
48 00 61 00 6C 00 6C 00 6F 00 ?
Nun, technisch gesehen, ja, glaube ich. Es war auch wirklich so, dass die ersten Entwickler Unicode-Kode-Nummern im Big- und im Little-Endian-Modus speichern können wollten, je nachdem was ihre jeweilige CPU am schnellsten konnte ... Und es war Abend, und es war Morgen, und schon gab es zwei Arten, Unicode zu speichern. Und so sah man sich zu der bizarren Konvention gezwungen, jeden Unicode-Text mit einem FE FF zu beginnen. Das nennt man Byte-Reihenfolge-Marke und wenn du hohe und niedrige Bytes austauschst, steht dort FF FE und jeder, der das liest, weiß, dass er alle Bytes paarweise vertauschen muss. Puh! In freier Wildbahn besitzt aber nicht jeder Unicode-Text eine Byte-Reihenfolge-Marke.
Eine Zeitlang schien diese Lösung gut genug, doch die Programmierer meckerten rum. "Guckt euch diese ganzen Nullen an!" sagten sie, denn es waren Amerikaner und sie sahen englischen Text, wo naturgemäß kaum Kode-Nummern oberhalb von U+00FF vorkommen. Außerdem waren es alternative Hippies aus Kalifornien, die sparen und bewahren (Grins) wollten. Texaner hätten bedenkenlos nochmal soviele Bytes weggeschlürft. Diese kalifornischen Weicheier aber ertrugen den Gedanken nicht, für Text die dopplete Anzahl von Bytes zu verbrauchen. Außerdem gab es ja überall diese Massen an Dokumenten in all diesen ASCII- und DBCS-Zeichensätzen - wer sollte die alle konvertieren? Ich jedenfalls nicht! Darum entschieden sich die meisten, Unicode einfach zu ignorieren; das ging jahrelang so und das Tohuwabohu wurde zum Ärgernis.
Dann entwickelte jemand das brilliante Konzept UTF-8. Das war ein weiteres System, eine Kette von Unicode-Kode-Nummern, also diese mysteriösen U+-Nummern, mittels 8-Bit-Bytes zu speichern. In UTF-8 werden alle Kode-Nummern von 0-127 in einem Byte gespeichert. Nur die Kode-Nummern 128 und höher werden in 2, 3 und sogar bis zu 6 Bytes gespeichert.
Das war so geschickt, dass englische Texte in UTF-8 genauso aussehen, wie vorher in ASCII, und deshalb den Amerikanern überhaupt nichts auffiel. Nur der Rest der Welt musste durch den Reifen springen. Das Beispiel Hallo, also U+0048 U+0061 U+006C U+006C U+006F, würde als 48 61 6C 6C 6F gespeichert, was - sieh mal einer an - genauso war wie in ASCII und ANSI und jedem OEM-Zeichensatz auf unserem Planeten.
Bist du aber so dreist und benutzt griechische oder klingonische Buchstaben, dann musst du auch mehrere Bytes zum Speichern einer einzigen Kode-Nummer hernehmen - und nur die Amis merken nix. (UTF-8 hat auch die nette Eigenschaft, dass ignorante, altmodische Textverarbeiter, die noch mit dem Null-Terminator als Textendezeichen arbeiten, das auch weiterhin tun können.)
Bis hierher war also die Rede von drei Arten, Unicode zu kodieren. Die traditionellen 2-Byte-Methoden, genannt UCS-2 (weil 2 Byte) oder UTF-16 (weil 16 Bit), in den Varianten Big-Endian-UCS-2 und Little-Endian-UCS-2. Und den allseits beliebten neuen UTF-8-Standard, der auch dann respektabel funktioniert, wenn englischer Text und hirntote Programme, die nichts anderes als ASCII kennen, aufeinandertreffen.
In Wahrheit gibt es der Methoden noch einige mehr. Da ist zum einen UTF-7, das UTF-8 ziemlich ähnelt, aber garantiert, dass das hohe Bit immer Null ist; so kann man Unicode-Text auch unbeschadet durch diktatorische Polizeistaat-E-Mail-Systeme schleusen, die denken, das 7 Bit genug sind, Danke! Dann gibt's da noch UCS-4, das jede Kode-Nummer in 4 Bytes speichert, mit der netten Eigenschaft, dass für jede Ziffer der Kode-Nummer ein Byte zur Verfügung steht, aber, Himmel!, selbst in Texas würden sie nicht soviel Speicherplatz verschwenden.
Und in der Tat, wo wir jetzt schon im platonischen Sinne über ideale Buchstaben reden, die durch Unicode-Kode-Nummern repräsentiert werden: diese Kode-Nummern kann man auch nach den alten Kodierregeln verschlüsseln! Zum Beispiel könnte man die Unicode-Zeichenkette für Hallo (U+0048 U+0061 U+006C U+006C U+006F) mit ASCII schlüsseln, oder dem alten griechischen, oder dem hebräischen ANSI-Zeichensatz, oder in jedem anderen von den hunderten bisher erfundenen. Die Sache hätte nur einen Haken: einige der Buchstaben könnte man nicht sehen! Solange es kein Äquivalent der Unicode-Kode-Nummer im jeweiligen Zeichensatz gibt, sieht man üblicherweise nur ein kleines Fragezeichen: ? oder, wenn du wirklich Glück hast, eine Schachtel. Was siehst du? -> �
Es gibt mehrere hundert traditionelle Kodierungen, die nur einige Kode-Nummern richtig speichern und alle anderen zu Fragezeichen machen. Populäre Kodierungen für englischen Text sind: Windows-1252 (das ist der Windows 9x-Standard für westeuropäische Sprachen und ISO-8859-1, auch bekannt als Latin-1 (das hilft ebenfalls bei alle westeuropäische Sprachen weiter). Versuchst du aber russische oder hebräische Buchstaben damit zu kodieren, bekommst du nur Fragezeichen. Aber UTF 7, 8, 16 und 32 können jede Kode-Nummer korrekt speichern!
Und wenn du alles wieder vergessen solltest, was ich bisher erklärt habe, Bitte! merk' dir wenigstens das: Eine Zeichenkette ist ohne Sinn, wenn man die Kodierung nicht kennt. Du kannst nicht länger den Kopf in den Sand stecken und so tun als sei "reiner Text" ASCII.
Es gibt keinen "reinen Text".
Zu einer Zeichenkette im Speicher, in einer Datei oder in einer Elektropost musst du auch die Kodierung kennen; andernfalls kannst du sie weder richtig interpretieren noch für den Benutzer richtig anzeigen.
Fast jedes dumme "Meine Internetseite sieht aus wie Kauderwelsch" oder "Sie kann meine E-Mails nicht lesen, wenn ich Akzente benutze" lässt sich auf einem naiven Programmierer zurückführen, der nicht verstanden hat, dass, wenn er mir nicht sagt, ob ein bestimmter Text in UTF-8 oder ASCII oder ISO 8859-1 (Latin 1) oder Windows 1252 (westeuropäisch) kodiert ist, ich ihn einfach nicht richtig anzeigen kann, ja, noch nicht einmal sagen kann, wo ein Buchstabe zu Ende ist. Es gibt mehrere hundert Kodierungen, und oberhalb der Kode-Nummer 127 gibt's keinen festen Grund mehr.
Wie konserviert man die Information über die Kodierung? Nun, das ist standardisiert. Bei E-Mails wird das in den Kopfdaten in dieser Form erwartet:
Content-Type: text/plain; charset="UTF-8"
Für eine Internetseite war die ursprüngliche Idee, dass der Server einen ähnlichen http-Content-Type sendet - also nicht im HTML, sondern in den Kopfdaten, die vor dem HTML gesendet werden.
Das führt zu Problemen. Angenommen ein Server betriebe viele Netzplätze, hunderte von Seite würden von vielen Leuten in vielerlei Sprachen beigesteuert, jeweils in der Kodierung die ihre jeweilige Version von Microsoft FrontPage zu erzeugen in der Lage wäre. Der Server selbst könnte gar nicht wissen, in welcher Kodierung jede Datei geschrieben ist, könnte also auch keinen Content-Type-Kopf vornewegschicken.
Es wäre also angenehm, wenn man den Content-Type der HTML-Datei direkt ins HTML schreiben könnte, innerhalb einer besonderen Marke. Natürlich trieb das die Puristen auf die Barrikaden... wie kann man denn die HTML-Datei lesen, bevor man weiß, wie sie kodiert ist?! Glücklicherweise tun fast alle verbreiteten Kodierungen zwischen 32 und 127 dasselbe, so dass man immer weit genug im HTML-Kode kommt, ohne lustige Buchstaben zu gebrauchen.
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Aber diese meta-Marke muss wirklich das Allererste im <head>-Block sein, denn sobald der Browser diese Marke sieht, hört er mit Analysieren auf und beginnt erneut von vorne - jetzt mit der neuen Kodierinformation.
Was tun Browser wenn sie keinen Content-Type finden, weder im http-Kopf noch in einer meta-Marke? Der Internet Explorer tut etwas wirklich interessantes: er versucht, Sprache und Kodierung zu erraten, und zwar aufgrund der Häufigkeit, in der verschiedene Bytes in typischen Texten typischer Kodierungen der verschiedenen Sprachen auftreten.
Weil die verschiedenen 8-Bit-Zeichensätze ihre nationalen Buchstaben zwischen 128 und 255 in verschiedenen Bereichen parken, und weil jede Sprache eine charakteristische Häufigkeitsverteilung ihrer Buchstaben hat, funktioniert das ziemlich gut.
Es ist wirklich sonderbar, doch es scheint oft genug zu passieren: Ein naiver Webdesigner schaut sich seine gerade erstellte Seite im Browser an und sie schaut gut aus; eines Tages schreibt er etwas, das der Häufigkeitsverteilung der Buchstaben in seiner Sprache nicht entspricht, und der Internet Explorer entscheidet sich für Koreanisch und zeigt entsprechendes an, was beweist, so meine ich, dass das Postelsche Gesetz (Sei konservativ mit dem, was du sagst, und liberal mit dem, was du akzeptierst) kein gutes Ingenieursprinzip ist. Egal. Was tut die arme Leserin dieser Seite, die in Bulgarisch geschrieben wurde, aber jetzt ausschaut wie Koreanisch (noch nicht mal richtiges Koreanisch) ? Sie benutzt im Menu den Punkt Ansicht>Kodierung und probiert eine ganze Reihe von Kodierungen durch bis das Bild klarer wird (es gibt mindestens ein Dutzend für osteuropäische Sprachen)... wenn sie weiß, dass man das tun kann... was aber die allerwenigsten wissen!
Für die letzte Version von CityDesk, einer Verwaltungssoftware für Internetseiten, die meine Firma entwickelt und vertreibt, entschieden wir uns, intern alles in UCS-2(zwei Byte)-Unicode zu kodieren, wie es auch Visual Basic, COM und Windows NT/2000/XP standardmäßig tun. In C++ deklarieren wir Zeichenketten einfach als wchar_t ("wide char") anstatt char und dann benutzen wir die wcs-Funktionen anstatt der str-Fuktionen (z.B.: wcscat und wcslen statt strcat und strlen). Ein Literal in C wird UCS-2, wenn man ein L davor setzt, also so: L"Hallo".
Wenn CityDesk eine Internetseite publiziert, konvertiert es alles nach UTF-8, was Browser schon seit einigen Jahren unterstützen. Auf diese Weise werden alle 29 Sprachversionen von Joel on Software kodiert, und bis jetzt habe ich noch von keinem gehört, der Probleme beim Anschauen gehabt hätte.
Dieser Aufsatz ist ziemlich lang geworden, und ich kann hier nicht alles abdecken, was es über Kodierungen und Unicode zu wissen gibt. Ich hoffe aber, wenn du bis hierher gelesen hast, dass du jetzt genug weißt, um weiter an deinem Programm herumzudoktern - mit Antibiotika und nicht mit Quecksilber oder Zaubersprüchen. An die Arbeit!