Teil von SELFHTML aktuell Teil von Artikel Teil von JavaScript Teil von UTF-8 und base64

UTF-8 und der Unicode in JavaScript

nach unten Unicode und seine UTF-8 Notation
nach unten Unicode und UTF-8 in HTML
nach unten Der UTF-8 Encoder in JavaScript
nach unten Der UTF-8 Decoder in JavaScript
nach unten UTF-8 Codierung als Beispiel
nach unten Abschließende Betrachtung zu UTF-8

Unicode und seine UTF-8 Notation

Im Gegensatz zu den Zeichensätzen der iso-8859-Familie basiert Unicode nicht auf 8 Bit langen Repräsentationen der Zeichen, sondern auf 16 Bit langen Repräsentationen. Dies ermöglicht eine Darstellung von 216 = 65536 Zeichen. Seit der Version 3.0 des Unicode-Standards gibt es auch Zeichen mit Werten jenseits dieser Grenze. Das Unicode-Consortium, auf dessen englischsprachige Seite Hompage eine genauere Beschreibung zu finden ist, wacht darüber, welchem Zeichen welcher Code zugewiesen wird. Das Unicode-System umfasst nicht nur sprachspezifische Zeichen, wie sie zum Beispiel für die Darstellung von japanischen Schriften benötigt werden, sondern auch technische Symbole oder Symbole, welche zur Darstellung mathematischer Operationen (Wurzelziehung, Integral usw.) dienen.

So schön die Vorstellung eines solchen Systems auch ist, so schwierig sind die Details seiner Implementierung. Zu allererst muss das Betriebssystem Unicode unterstützen, was bei den meisten heutigen Systemen zum Glück der Fall ist. Schwieriger sieht es da schon mit der Font-Unterstützung aus. Erst neuere Schriftsätze sind in der Lage, mehrere Schriftsysteme darzustellen. Außerdem ist es eine Verschwendung von Speicherplatz, jedem Zeichen 16 Bit zur Verfügung zu stellen. Normale Textdateien wären plötzlich exakt doppelt so groß, verglichen mit der 8bit-Schreibweise. Die Speicherverschwendung ist besonders extrem, wenn Dokumente nach wie vor aus den Zeichen bestehen, welche sich durch die 8bittige Schreibweise der iso-8859-Zeichensätze repräsentieren lassen.

Als Beispiel die binäre Repräsentation des Buchstaben "a" mit dem Charcode 97:

Zeichenformat Bitnotation
8-bit Schreibweise (z.B. iso-8859-1) 01100001
16-bit Schreibweise (Unicode) 0000000001100001

Das Beispiel verdeutlicht den Overhead an führenden Nullen bei 16-Bit-Notierung, besonders bei lateinischen Zeichen. Diese Zeichen werden sehr häufig verwendet, haben allerdings einen geringen Charcode. Aus diesem Grund wurde die UTF-8 Codierung ersonnen. UTF-8 codiert Zeichen nur bei Bedarf mit mehr als 8 Bit. Dazu wird eine Maskierungstechnik angewandt, die ähnlich der Maskierung von Steuerzeichen bei JavaScript funktioniert, nur auf binärer Ebene. Somit werden bei UTF-8 nur die ersten 127 Zeichen des Unicode-Zeichensatzes mit 8 Bit codiert. Die übrigbleibende "1" dient der Maskierung:

Bitnotation Erklärung
01000001 die führende Null definiert das Byte als Zeichen, ein "A"
10110010 die führende 1 definiert das Byte als Teil eines Zeichens, welches nur im Zusammenhang mit anderen Bytes gelesen werden kann.

Diese Maskierung erlaubt folgende Möglichkeiten:

Der Unicodebereich von 0-127 wird durch ein Byte repräsentiert. Ein Zeichen aus dem Bereich von 128-2047 wird durch 2 Byte repräsentiert. Der letzte Bereich schließlich von 2048-65536 wird durch 3 Byte repräsentiert. Kommen wir nun zu den Regeln für die Maskierung. Es wurde schon gesagt, dass eine führende "1" etwas maskiert. Die Frage ist, was maskiert wird und nach welchen Regeln. Hier folgen die von UTF-8 genutzten Maskierungssequenzen.

1. "10" bedeutet: dieses Zeichen beinhaltet Füllbits und ist nur im Zusammehang mit vorangegangen Zeichen lesbar.
2. "110" dieses Zeichen leitet ein Unicodezeichen bestehend aus 2 Byte ein.
3. "1110" dieses Zeichen leitet ein Unicodezeichen bestehend aus 3 Byte ein.
(4.) eigentlich ist die führende "0" auch eine Maskierung, da sie keinen wirklichen Wert darstellt.

Folgende Tabelle soll die Verteilung der einzelnen Bits innerhalb eines UTF-8 Zeichens erklären. Die ausgeschriebenen Bits zeigen die Maskierung, die "x" stehen als Repräsentanten des eigenlichen Wertes.

Zeichenbereich Bitnotation
Unicode-Range von 0-127 0xxx.xxxx
Unicode-Range von 128-2047 110x.xxxx  10xx.xxxx
Unicode-Range von 2048-65536 1110.xxxx  10xx.xxxx  10xx.xxxx

Zum besseren Verständnis soll uns hier das Wort "Füße" dienen, da es 2 Umlaute aus dem Unicodebereich von 128 bis 2047 enthält. Als erstes nehmen wir es in seine dezimalen Bestandteile auseinander: F(70), ü(252), ß(223), e(101). Hierzu ist anzumerken, dass der Western-Latin-1 Zeichensatz, also iso-8859-1 mit den ersten 256 Zeichen des Unicode-Zeichensatzes identisch ist. Die binäre Repräsentation liest sich so:

Zeichenbereich Bitnotation
Binär nach iso-8859-1 0100.0110(F)  1111.1100(ü)            1101.1111(ß)            0110.0101(e)
Binär nach Unicode 0100.0110(F)  1100.0011 1011.1100(ü)  1100.0011 1001.1111(ß)  0110.0101(e)

Deshalb würde sich ein iso-8859-1 codierter Text in einem normalen (8-Bit-orientierten) Texteditor folgendermaßen lesen:
Füße, während der UTF-8 codierte Text so aussehen würde:
Füße

nach obennach unten

Unicode und UTF-8 in HTML

Seit der Einfürung des HTML-Standards 4.0 ermöglicht die Verwendung des optionalen Meta-Tags <meta http-equiv="Content-Type" content="text/html; charset=xxxxxxxx">, dass UTF-8 codierter Text direkt in die Webseite ein gearbeitet werden kann. Dazu muss gesagt werden, dass die Lesbarkeit des Quellcodes zwischen eingeschränkt und unlesbar schwankt, abhängig vom Anteil der Zeichen mit höherem Charcode, welche maskiert werden.

Beispiel:

Popup-Seite Anzeigebeispiel: so sieht's aus

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
        <title>Unicode-Test (UTF-8 eingebunden in HTML)</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

<body>
Dieses Dokument dient der Anzeige von Sonderzeichen im Unicodeformat.
<br><br>
europäische Sonderzeichen:<br>
ß, ü, Ü, ä, Ä, ö, Ö, €
<br><br>
Beispiel eines nichteuropäischen Zeichenstzes(kyrillische Kleinbuchstaben):<br>
абвгдежзийклмнопрстуфхцчшщъыьэюя<br><br>

Beispiel f&uuml;r Zeichenmodifizierungen<br>
ẦầẨẩẪẫẴẵẶỀềỂểỄễỆệỈỉỖỗỘộỚớỜờỞởỠỡỢỦủỨứỪừỬửỮữỰựỲỳỴỵỶỷỸỹ
<br><br><br><br>

</body>
</html>

Erläuterung:

Dieses Html-Dokument macht sich den Meta-Tag zunutze und weist den Browser mittels charset=utf-8 an, das Dokument im UTF-8 Format anzuzeigen.

nach obennach unten

Der UTF-8 Encoder in JavaScript

Die Struktur der UTF-8 Codierung verlangt nach der Verwendung von Bit-Operatoren, die in SELFHTML im Abschnitt bereichsübergreifende Seite Bit-Operatoren aufgeführt sind.

Beispiel: Der UTF-8 Encoder

00:        function encode_utf8(rohtext) {
01:             // dient der Normalisierung des Zeilenumbruchs
02:             rohtext = rohtext.replace(/\r\n/g,"\n");
03:             var utftext = "";
04:             for(var n=0; n<rohtext.length; n++)
05:                 {
06:                 // ermitteln des Unicodes des  aktuellen Zeichens
07:                 var c=rohtext.charCodeAt(n);
08:                 // alle Zeichen von 0-127 => 1byte
09:                 if (c<128)
10:                     utftext += String.fromCharCode(c);
11:                 // alle Zeichen von 127 bis 2047 => 2byte
12:                 else if((c>127) && (c<2048)) {
13:                     utftext += String.fromCharCode((c>>6)|192);
14:                     utftext += String.fromCharCode((c&63)|128);}
15:                 // alle Zeichen von 2048 bis 66536 => 3byte
16:                 else {
17:                     utftext += String.fromCharCode((c>>12)|224);
18:                     utftext += String.fromCharCode(((c>>6)&63)|128);
19:                     utftext += String.fromCharCode((c&63)|128);}
20:                 }
21:             return utftext;
22:         }

Erläuterung:

Die Funktion encode_utf8() erwartet einen ganz normalen String. Dieser String wird mittels einer for()-Schleife Zeichen für Zeichen durchgegangen. Des einfacheren Handlings wegen wird in Zeile 7 der Charcode des aktuellen Zeichens ermittelt und in einer lokalen Variable c gespeichert. Diesen Wert erhalten wir von der Funktion bereichsübergreifende Seite charCodeAt(), welche in JavaScript 1.2 eingeführt wurde. In dieser Version lieferte sie noch den Charcode des Western-Latin-1 Zeichensatzes zurück. Seit der Version 1.3 allerdings gibt sie Unicode zurück und liefert somit die Grundlage des hier beschriebenen Verfahrens.
Danach wird anhand der Größe des Charcodes geprüft, ob die UTF-8 Codierung 1,2 oder 3 Byte beansprucht und der entsprechende Code ausgeführt. In diesem Code schließlich wird mittels der Bitoperatoren und der String.fromCharCode()-Funktion der UTF-8-codierte String zusammengestellt.

Anhand der Codierung des EURO-Zeichens, welches den Charcode 8364 hat, soll hier die Vorgehensweise der Routine beschrieben werden. Als erstes benötigen wir zur Veranschaulichung die binäre Repräsentation: 0010000010101100. Aus der Größe der Zahl wird klar, dass hier 3 Byte zurückgegeben werden müssen. Dazu wird der Code aus Zeile 17 bis 19 verwendet. Laut Spezifikation ist es das Ziel, die ersten 4 Bit auf das erste Zeichen, die folgenden 6 auf das mittlere Zeichen und die letzten 6 auf das letzte Zeichen zu verteilen. Außerdem muss die Maskierung mitberechnet werden. Wenden wir uns den ersten 4 Bit zu. Diese erhalten wir durch eine Bitverschiebung um 12 Zeichen (c>>12) nach rechts. Das Ergebnis ist 0010. Die notwendige Maskierung wird durch die Zahl 224 (binär 1110.0000) zur Verfügung gestellt. Der oder-Operator | fügt es zusammen. 1110.0000 | 0000.0010 = 1110.0010 = 226. Damit wäre des erste Byte geschafft.

Jetzt gilt es, die 6 Bit aus der Mitte zu isolieren und wieder mit dem |-Operator zu maskieren. Der Wert für die Maskierung ist diesmal allerdings 1000.0000 = 128. Wenden wir uns der Isolierung der bewussten 6 Bit zu: 0010000010101100. Dazu verschieben wir die rechten 6 Bit ins Abseits. Dies passiert äquivalent dem ersten Teil c>>6, und wir erhalten 0010000010. Eine &-Verknüpfung mit 63 (111111) stanzt schließlich die gesuchten 6bit aus. Nach der Maskierung ist das Ergebnis 10000010 = 130.

Für die letzten 6 Bit reicht die &-Verknüpfung mit (111111). Die |-Verknüpfung mit 128 komplettiert auch dieses Byte mit dem Wert 10101100 = 172.

nach obennach unten

Der UTF-8 Decoder in JavaScript

Die Rückwandlung des UTF-8 codierten Strings in Plaintext erfolgt ähnlich dem in der Encodierung angewandten Verfahren. Zunächst der Quelltext.

Beispiel: Der UTF-8 Decoder

00:        function decode_utf8(utftext) {
01:             var plaintext = ""; var i=0; var c=c1=c2=0;
02:             // while-Schleife, weil einige Zeichen uebersprungen werden
03:             while(i<utftext.length)
04:                 {
05:                 c = utftext.charCodeAt(i);
06:                 if (c<128) {
07:                     plaintext += String.fromCharCode(c);
08:                     i++;}
09:                 else if((c>191) && (c<224)) {
10:                     c2 = utftext.charCodeAt(i+1);
11:                     plaintext += String.fromCharCode(((c&31)<<6) | (c2&63));
12:                     i+=2;}
13:                 else {
14:                     c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2);
15:                     plaintext += String.fromCharCode(((c&15)<<12) | ((c2&63)<<6) | (c3&63));
16:                     i+=3;}
17:                 }
18:             return plaintext;
19:         }

Erläuterung:

Mit der charCodeAt()-Methode wird der Charcode des aktuellen Zeichens aus dem String geholt. Danach wird anhand des Zeichenwerts entschieden, ob es sich um 1, 2 oder 3 Byte pro Zeichen handelt. Dieser Code verifiziert nicht, ob es sich um ein gültiges UTF-8 Zeichen handelt. Das bedeutet: sollte der Decoder davon ausgehen, dass laut der Spezifikation ein 3-teilges UTF-8 Zeichen vorliegt, aber nur 2 Teile vorhanden sind, kann es im schlimmsten Fall zu einem JavaScript-error mit all seinen Folgen kommen. Prinzipiell ist die Verifizierung nicht schwierig, allerdings ist die Seite Performance von JavaScript nicht unbedingt so gut. Deshalb wurde in diesem Beispiel darauf verzichtet.

Die Verarbeitung der einzelnen Bits erfolgt analog zur Encodierung. Mittels der &-Verknüpfung mit einer Bit-Folge aus mehreren Einsen werden die relevanten Bits ausgestanzt, dann an ihre Position verschoben und schließlich mit der |-Verknüpfung zusammengefügt. Die String.fromCharCode()-Methode liefert das aktuelle Zeichen zurück. Der Iterator i wird entsprechend der Anzahl der zu bearbeitenden Bytes erhöht, um zum nächsten gültigen Zeichen zu gelangen.

nach obennach unten

UTF-8 Codierung als Beispiel

Kopieren sie folgendes Textbeispiel in das Formularfeld unterhalb davon, und wandeln Sie die Zeichen nach UTF-8 um und zurück. Beachten Sie bitte, dass das Ergebnis schlecht lesbar ist, und dass einige Zeichen auch nicht darstellbar sind, da sie Steuerzeichen sind (Coderange 129-159). Grundsätzlich ist anzumerken, dass neuere Browser mehr Zeichen darstellen können, als beispielweise die Netscape-Browser der Generation 4.

europäische Sonderzeichen:
ß, ü, Ü, ä, Ä, ö, Ö, €

Beispiel eines nichteuropäischen Zeichenstzes(kyrillische Kleinbuchstaben):
абвгдежзийклмнопрстуфхцчшщъыьэюя

Beispiel für Zeichenodifizierungen
ẦầẨẩẪẫẴẵẶỀềỂểỄễỆệỈỉỖỗỘộỚớỜờỞởỠỡỢỦủỨứỪừỬửỮữỰựỲỳỴỵỶỷỸỹ


  

nach obennach unten

Abschließende Betrachtung zu UTF-8

Die Konvertierung nach UTF-8 alleine ist noch nicht besonders sinnvoll, obwohl sie es bereits ermöglicht, Zeichen mehrerer Zeichensätze inklusive naturwissenschaftlicher und technischer Sonderzeichen in einem String unterzubringen. Doch besonders auf dem Weg zum Server und zurück ist ein Zeichensatz aus 256 Zeichen nicht besonders robust. Hier setzt die base64-Codierung an, welche wir im zweiten Teil behandeln. Zuvor jedoch noch folgende Überlegung: Die Wandlung von Strings ist besonders in Netscape 4.x nicht besonders sicher, das stellt man schnell bei der Verwendung des obigen Beispiels fest. Eine Möglichkeit, dies zu umgehen, stellt die Verwendung von Arrays dar. Diese sind nach Meinung des Autors auch performanter, besonders bei der Konvertierung großer Texte. Wie diese Arrays, die eine Rückwandlung der Zahlen in Strings umgehen, implementiert werden, wird auf der nächsten Seite behandelt.

Ein interessanter Aspekt bliebe noch zu erwähnen. Da wir Hin- wie Rückwandlung des UTF-8 Codes von Hand erledigen, sind wir nicht an die Konventionen gebunden. Wenn man sich die Spezifikation ansieht, wird man festellen, dass Zeichen, deren Charcode in UTF-8 binär mit 1111 beginnen, ungültig sind. Diese 16 Zeichen mit dem Charcode zwischen 240 und 256 kann man nun seinerseits "missbrauchen". Zum Beispiel als Trenner zwischen Formularfeldern, oder als Gleichheitszeichen zwischen Wert und Bezeichner. Natürlich müssen diese Zeichen vor der Rückkonvertierung wieder aus dem Strom entfernt werden.

weiter Seite Performance

zurück Seite Vorwort

Teil von SELFHTML aktuell Teil von Artikel Teil von JavaScript Teil von UTF-8 und base64

© 2007 bereichsübergreifende Seite Impressum
© 2000-2005 E-Mail tobias@justdreams.de für den Text auf dieser Seite