Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.de  
   

Die mobilen Seiten von c++.de:
http://m.c-plusplus.de
Infos hier [BETA]

  
c++.de :: Die Artikel ::  Einführung in Codepages und Unicode  
Gehen Sie zu Seite 1, 2  Weiter
  Zeige alle Beiträge auf einer Seite
Auf Beitrag antworten
Autor Nachricht
Jochen Kalmbach
Moderator

Benutzerprofil
Anmeldungsdatum: 11.11.2005
Beiträge: 12132
Beitrag Jochen Kalmbach Moderator 09:21:28 10.10.2006   Titel:   Einführung in Codepages und Unicode            Zitieren

1 Kleiner historischer Abriss

Schon vor langer Zeit haben die Menschen das Bedürfnis gehabt etwas aufzuschreiben. Damals hat man noch Schildkröten statt Papier verwendet, aber vom Prinzip hat sich bis heute nicht viel daran geändert.

Bis die Computer kamen... dann hatte man das Problem, dass die Schriftzeichen sich so schwer in den Bildschirm ritzen ließen. Dieses Problem hatte man übrigens nicht erst mit den Computern sondern auch schon früher, als man versucht hat Nachrichten ohne Steintafeln/Papier zu übertragen (z.B. via Optische-Telegraphie).

Es musste also eine Codierung für die Zeichen her. Upps, da wir gerade bei Zeichen sind: Das was umgangssprachlich als "Zeichen" verstanden wird, ist i.d.R. ein "Glyph".

Aber nun zurück zu den Computern und das Problem mit den Zeichen... Der Computer versteht (bis heute noch) nur Bits (also 0 und 1), also musste etwas erfunden werden, was sich mit 0 und 1 darstellen lässt. Einer der ersten Zeichensätze für solche Zwecke war der "Baudot-Code" (obwohl es zu dieser Zeit noch gar keinen Computer gab). Davon abgeleitet wurde dann der CCITT-2 Code zum Standard in der Telex Übertragung (welches heute noch in Deutschland existiert!).

Erst 1967 wurde dann der ASCII-Standard definiert, welcher aus 7-Bit besteht und noch heute Gültigkeit hat und die Basis für die meisten Codierungen ist.

Mehr Infos zur Historie gibt es auch bei Heise.

2 Die Folgen von ASCII

Der ASCII-Zeichensatz hatte ein großes Problem: Er wurde von den Amerikanern definiert, deren Horizont kaum über den Teich reicht. Somit wurden nur Zeichen aufgenommen, die drüben bekannt waren. Nicht einmal Zeichen die in Europa oft verwendet werden (z.B. ä, ö, ü, ß, á, à, usw.) wurden berücksichtigt.
Aus dieser Not heraus wurden diverse Codepages erfunden. Die meisten dieser Codepages (also Kodierungen, welche mehr als nur ASCII-Zeichen enthalten) wurden durch die ISO in der Norm ISO 8859 standardisiert. Diese Codepages bestehen aus 8-Bit und können somit 2^8 Zeichen (256) adressieren. Die meisten dieser Codepages verwenden in den ersten 128 Zeichen dieselben Zeichen wie im ASCII-Standard. Somit sind nur die letzten 128 Zeichen in einer Codepage unterschiedlich. Die ISO hat aber nur Zeichensätze aufgenommen, die im westlichen Umfeld gebräuchlich waren. Damit wurden auch hier keine asiatischen Zeichen kodiert, welches wiederum diverse länderspezifische Kodierungen hervorbrachte (z.B. Big5, GB2312 und Shift-JIS).

Auch in Windows halten sich alle Codepages in den ersten 128 Bytes an den ASCII-Standard und nur die oberen 128 Bytes sind Codepage-Spezifisch (naja, es gibt auch Ausnahmen: so hat z.B. die Windows Codepage 932 (Japanisch) oder 949 (Koreanisch) an der Position 0x5C nicht ein Backslash sondern ein YEN bzw. ein WON Zeichen; siehe auch: When is a backslash not a backslash?).

2.2 Kodierung von mehr als 256 Zeichen

Durch die Einschränkung auf 256 Zeichen (SBCS: Singlebyte Character Set; es wird nur ein Byte für ein Zeichen benötigt), welche aus den gebräuchlichen 8-Bit für ein Zeichen hervorgeht, haben alle Sprachen Probleme, welche mehr als 256 Zeichen verwenden. Um dieses Problem zu lösen wurde das Multibyte Character Set erfunden.

Bei MBCS wird für ein Zeichen min. 1 Byte verwendet. Es gibt aber auch viele Zeichen, für die 2 und mehr Bytes verwendet werden (bei solchen Zeichen werden die ersten Bytes als "Lead-Byte" bezeichnet). Mit dieser Technik können beliebig viele Zeichen kodiert werden.

In der Praxis gibt es kaum MBCS-Zeichensätze. Die meisten Kodierungen verwenden nur DBCS (Doublebyte Character Set) welcher maximal 2 Byte zur Kodierung eines Zeichens verwenden (so z.B. auch alle unterstützten MBCS von Microsoft).


2.2 MBCS und die Auswirkungen auf die Programmierung

Das Handhaben von Zeichenketten war in C schon immer ein Problem. Neben den vielen Programmierfehlern bzgl. zu kleiner Puffer ist mit dem Aufkommen der MBCS noch ein weiteren Problem dazugekommen, welches sich viele Programmierer nicht bewusst sind: Ein Zeichen kann aus mehreren Bytes bestehen!

Vielen werden jetzt zwar denken: Was geht mich das an! Im Folgenden will ich versuchen darauf einzugehen (PS: Man kann das Problem natürlich auch ignorieren, nur wird man dann bestimmte Gebiete (z.B. Asien) von seinem Programm ausschließen).


2.2.1 Wie lang ist ein MBCS-String?

Hier scheiden sich schon die Geister: Was ist denn genau gemeint? Will der Fragende die Anzahl der Zeichen (Glyphs?) wissen oder die Anzahl der Bytes? Will man z.B. in einer Textverarbeitung die Anzahl der Zeichen zur Information anzeigen (wie es ja einige machen), so will man sicherlich die Anzahl der "Glyphs" wissen. Bei einem MBCS-Zeichensatz kann dies aber weniger sein, als der String in Bytes lang ist. Somit ist ein einfaches strlen nicht mehr ausreichend. Man muss also immer noch etwas über den tatsächlich verwendeten Zeichensatz wissen um die Anzahl der "Glyphs" zu ermitteln. Hier taucht gleich das zweite Problem auf: Bei den MBCS sind nicht alle Byte-Folgen gültige Zeichen! So ist z.B. bei Shift-JIS die Zeichenfolge 0xE6 0x40 ein gültiges Zeichen wo hingegen 0xE6 0x7F kein definiertes Zeichen ist.

Leider gibt es für MBCS-Unterstützung keine (zumindest mir bekannte) Normierung in der C/C++-Welt. Microsoft hat hierfür eine eigene Erweiterung zur Verfügung gestellt, welches hier unterstützt (_mbslen (ohne Prüfung auf Gültigkeit des Strings) und _mbstrlen (mir Prüfung auf Gültigkeit des Strings)). In Linux (g++) sind mir keine Funktionen bekannt, welche die Anzahl der "Glyphs" für verschiedene Codepages zurückliefern (für UTF8 gibt es wohl g_utf8_strlen).


2.2.2 Suchen von Zeichen in Strings

Um z.B. ein bestimmtes Zeichen in einem String zu suchen, nimmt man ja i.d.R. strchr. Wenn z.B. nach einem Unterstrich (_) suchen will, so ist dies bei einem SBCS ja trivial. Bei einem MBCS wird es hingegen schon sehr komplex, da auch beachtet werden muss, ob das aktuelle Zeichen eine "Lead-Byte" ist und somit das nächste als kombiniertes Zeichen betrachtet werden muss. Ein einfaches Suchen mittels strchr ist somit nicht mehr möglich. In der Windows Codepage 936 (Simplified Chinese GBK), ist das Zeichen U+7BF2 (Besenstiel) als "0xBA 0x5F" kodiert. Dies würde bei einer simplen Suche nach "_" (0x5F) einen Treffer liefern, was offensichtlich falsch ist.

Die Probleme mit MBCS gehen noch viel weiter. So ist es z.B. für einen Benutzer einer Textverarbeitung etwas ungewöhnlich, wenn das Programm MBCS verwendet (vielleicht ohne dass der Programmierer dies weiß), wenn er mit dem Cursor (Caret) durch seinen Text geht (z.B. Pfeil nach rechts) und er für ein (MBCS) Zeichen zuerst "im" Zeichen landet und erst beim zweiten mal hinter dem Zeichen...


3 Die Lösung aller Probleme:UNICODE
(oder: Wie man noch mehr Probleme schafft)

Um das Problem zu lösen, dass immer mehr unterschiedliche Codepages aus dem Kraut schießen, wurde (erst) 1991 eine einheitliche Kodierung definiert, welche alle Sprachen / Zeichen aufnehmen sollte (History of Unicode 1.0). Dabei wurde schon der erste Fehler gemacht und man ging davon aus, dass 2 Byte (65536) Zeichen vollkommen ausreichen würden um alle Zeichen in der Welt abzudecken (Zitat: "16-bits wide because this provides a sufficient number of codes (65,536) to represent electronic text characters anticipated for the foreseeable future"). Naja, die "foreseeable future" war bei vielen doch wohl sehr beschränkt ;).

Zumindest wurde dem Projekt etwas Erfolg vorhergesagt, da sich alle großen Firmen (Apple, GO, IBM, Metaphor, Microsoft, NeXT, Novell, The Research Libraries Group and Sun Microsystems) beteiligten.

Die Probleme mit dem zu kleinen Adressraum wurden dann auch schon in der Version 2 behoben, indem zusätzliche 16 weitere gleich große Bereiche definiert wurden. Somit gehen die Codepoints von U+00000 bis U+10FFFF (und belegen bei 1-zu-1 Kodierung jetzt 32-Bit). Wir dürfen gespannt sein, wie lange dieser Namenraum ausreichen wird.

Die aktuelle Unicode-Version 5.0.0 enthält ungefähr 99.000 Zeichen.


3.1 Kodierungen von UNICODE

Für Unicode gibt es unterschiedliche Kodierungen. Alle beziehen sich aber auf die Codepoints (ein bestimmter Wert in dem Unicode-Coderaum; dieser Wert wird i.d.R. mittels U+xxxx oder U+10xxxx geschrieben), welche im Unicode-Standard definiert sind.

Es gibt die folgenden Kodierungen von Unicode:
- UTF-32 (BE oder LE)
- UTF-16 (BE oder LE)
- UTF-8
- UTF-7
- UTF-EBCDIC
- Punycode

Die gebräuchlichste Kodierung ist UTF-8. Aus Sicht der Komplexität von Unicode spielt es keine große Rolle, welche der UTF-x Kodierungen man verwendet (aber dazu später).


3.2 BOM

Beim Lesen/Schreiben von Dateien existiert immer das Problem, dass man wissen muss, in welcher Kodierung diese Datei gelesen/gespeichert werden soll. Das Unicode-Konsortium hat hierfür einen BOM definiert, welcher ganz am Anfang der Datei steht und die Kodierung (für Unicode) definiert. Mit diesem BOM können aber nur die Unicode-Kodierungen unterschieden werden. Es ist nichts allgemeingültiges, womit man auch andere Codepages unterscheiden könnte.

Wenn kein BOM vorhanden ist, so kann nicht eindeutig ermittelt werden, um welches Encoding es sich bei einer (Text-)Datei handelt. Es ist einzig eine "Statistische Analyse" des Textes möglich, wie es z.B. Notepad mittels der WinAPI-Funktion IsTextUnicode macht.


3.3 Blocks

Unicode definiert zusammengehörige Zeichen in einem Block. Ein Block ist z.B. der ASCII-Block (Basic-Latin U+0000 bis U+007F). Um für kommende Zeichen genug Platz zu lassen, entstehen natürlich in einigen Blöcken Lücken die freigelassen wurden. Daraus wird auch ersichtlich warum erst ca. 9% aller Codepoints vergeben sind.

3.4 Kategorien

Jedem Codepoint ist (min.) eine Kategorie zugeordnet. Es gibt insgesamt 33 Kategorien. Eine Kategorie ist z.B. "Letter, Lowercase" (Kleinbuchstabe). In dieser Kategorie sind z.B. 1634 Zeichen vorhanden.

3.5 Besonderheiten von Unicode

Unicode versucht alle Probleme die man mit Sprachen und deren dazugehörigen Zeichen hat zu lösen. Aus diesem Grunde ist auch der Standard schon fast unüberschaubar geworden und es gibt wohl nur wenige Menschen die alle Bereiche vollständig verstanden haben. Im Folgenden will ich einzelne dieser Besonderheiten vorstellen.

3.5.1 Zahlen

Viele Programme müssen mit Zahlen arbeiten. Aber eigentlich fast alle beschränken sich dabei auf die uns bekannten Zahlen 0-9. Im Unicode-Standard sind aber über 836 Codepoints als Zahlen (Numbers) definiert! So gibt es z.B. das Zeichen U+216B (ROMAN NUMERAL TWELVE), welches als Zahl mit dem Wert 12 definiert ist. 290 von diesen Zahlen sind als Decimal Digits definiert und können somit 1-zu-1 mit dem uns bekannten Stellenwertsystem (Digits 0-9) ausgetauscht werden. 210 Zahlen sind so genannte Buchstaben-Zahlen, also Zahlen, welche sich nicht direkt mit einem "Digit" (also Zahlen die durch ein Stellenwertsystem zwischen 0 und 9 verwenden lassen) darstellen lassen. Die dritte Kategorie von Zahlen sind Sonstige Zahlen. Diese beinhalten z.B. auch Brüche (z.B. Ein viertel (1/4)) oder die Zahl "eins weniger als die vorherige".

Leider existiert auch hier in der C/C++-Welt keine mir bekannte Bibliothek, welche diese Eigenheit von Unicode vollständig unterstützt. So "kennt" z.B. die Funktion iswdigit je nach verwendetem OS bestimmte Zahlen nicht...

Mein GCC 3.3.4 unter Linux v2.6.8 kennt z.B. die Zahl ARABIC-INDIC DIGIT FIVE nicht. Dagegen erkennt es das gleiche Programm unter Windows XP (die MS-CRT verwendet intern GetStringTypeW, welches viele Zeichen kennt).
C++:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <ctype.h>
 
int main()
{
  if (iswdigit(0x0035) == 0)
    printf("'DIGIT FIVE' ist KEINE Zahl");
  if (iswdigit(0x0665) == 0)
    printf("'ARABIC-INDIC DIGIT FIVE' ist KEINE Zahl");
  return 0;
}


Im .NET-Framework 2.0 gibt es in der Char-Klasse, viele für Unicode notwendige Hilfsfunktionen. So kann man z.B. mittels
C++:
1
2
3
4
5
6
7
8
9
10
using namespace System;
int main()
{
  Console::WriteLine("'DIGIT FIVE' hat den Zahlenwert {0}",
    Char::GetNumericValue(0x0035));
  Console::WriteLine("'ARABIC-INDIC DIGIT FIVE' hat den Zahlenwert {0}",
    Char::GetNumericValue(0x0665));
  Console::WriteLine("'ROMAN NUMERAL TWELVE' hat den Zahlenwert {0}",
    Char::GetNumericValue(0x216B));
}

die Zahlenwerte dieser Unicode-Codepoints ermitteln.

Am Ende des Artikels findet sich noch ein Beispiel, welches alle (in .NET 2.0) bekannten Zahlen mit den dazugehörigen Werten ausgibt.


3.5.2 Combining-Characters

Die uns geläufigen Zeichen wie ä, ö, ü usw. sind ja etwas besondere Zeichen. Das 'ä' ist ja eigentlich ein 'a' mit einem Diakritischem Zeichen. Und genau dieser Sachverhalt kann auch in Unicode abgebildet werden. Das Zeichen 'ä' kann entweder als ein verbundenes Zeichen (precomposed character) geschrieben werden (U+00E4) oder als Kombination aus einem Basiszeichen (a) und einem Diaresis (combining-character): U+0061 U+0308.

Ein Combining Character ist somit ein Zeichen, was immer mit einem Basiszeichen zusammen dargestellt wird. Es ist auch möglich, dass ein Basiszeichen mehrere Combining-Characters enthält. So ist im folgenden Bild das Zeichen U+1EAB als eine Kombination aus einem Basiszeichen und zwei Combining-Characters dargestellt:


Diese Tatsache ist vielen nicht bekannt. Man sollte sich dieses aber wirklich verinnerlichen! Daraus ergeben sich erhebliche Konsequenzen. So ist z.B. das Vergleichen von Strings nicht mehr so trivial und muss ganz anders ablaufen. Auch hat es Auswirkung auf die Eingabe von Zeichen und das "Durchgehen" des Textes mittels der Cursor-Tasten. Wurde z.B. das obige Zeichen U+1EAB als Combining-Character eingeben, welches aus 3 Codepoints besteht, so wäre es für den Benutzer einer Textverarbeitung sehr verwunderlich, wenn man drei mal die "Pfeil nach rechts" Taste drücken müssen, um hinter das Zeichen zu kommen.

Hier schließt sich auch wieder der Kreis zu den MBCS-Zeichensätzen. Hier hat man nämlich genau dasselbe Problem, dass ein Codepoint nicht unbedingt ein Zeichen (Glyph) sein muss. UTF8/16 kann man ja auch als MBCS ansehen und somit wird auch deutlich, warum es eigentlich egal ist wenn man nicht UTF-32 zur Speicherung der Zeichen verwendet: Ein Aufwand bei der Darstellung und dem "durchgehen" von Zeichen hat man so oder so. Deshalb kann es nicht als Nachteil angesehen werden, wenn man intern kein UTF-32 verwendet.


3.5.3 Compatibility characters

"Compatibility characters" sind Codepoints, welche nur aufgenommen wurden um eine bessere Kompatibilität mit schon bestehenden Codepages herzustellen und somit eine einfache Konvertierung von diesen Codepages nach Unicode und wieder zurück zu ermöglichen. Man unterschiedet dabei zwischen Codepoints welche sich 1-zu-1 auf ein anderen Codepoint mappen lassen (so z.B. U+F900 welches sich direkt nach U+8C48 mappen lässt) und solchen Zeichen, die eigentlich aus mehreren Zeichen bestehen (Composite Character) aber auch mit mehreren einzelnen Zeichen angezeigt werden können (Decomposed Character). Ein Beispiel hierfür ist "bar", welches natürlich auch aus den Einzelbuchstaben bar zusammengesetzt werden kann.


3.5.4 Normalization

Aus den obigen Ausführungen kann man erkennen, dass es für einen Satz fast beliebig viele Darstellungsmöglichkeiten gibt (z.B. Combining/Compatibility-Characters). Um dieses Wirrwarr etwas zu entzerren hat man sog. "Normalisierungs-Formen" eingeführt. Jeder String kann in solch eine Form überführt werden (ist auch Voraussetzung für den Textvergleich und die Sortierung; wobei die Normalisierung dabei nur ein Schritt ist!). Es gibt die folgenden Normalisierungen:

  • Normalization Form D (NFD, Canonical Decomposition):
    Spaltet zusammengesetzte Zeichen in die einzelnen Bestandteile auf (wenn möglich). So wird z.B. das Zeichen 'Ä' in die Darstellung als U+0041 U+0308 überführt.
  • Normalization Form C (NFC, Canonical Decomposition, followed by Canonical Composition):
    Ist das Gegenteil von NFD: Hier werden zusammengesetzte Zeichen bevorzugt (wenn möglich). So wird anstelle von 'LATIN CAPITAL LETTER A', 'COMBINING DIAERESIS' das Zeichen 'LATIN CAPITAL LETTER A WITH DIAERESIS' verwendet.
    Auch werden veraltete, kombinierte Zeichen durch neue ersetzt, z.B. 'ANGSTROM SIGN' wird durch 'LATIN CAPITAL LETTER A WITH RING ABOVE' ersetzt.
  • Normalization Form KD (NFKD, Compatibility Decomposition):
    Ist wie "Form D", nur werden "compatibility character" durch richtige (aktuelle) Zeichen ersetzt. So wird z.B. anstelle aus dem einen Zeichen fi die zwei Einzelbuchstaben "fi" gemacht.
  • Normalization Form KC (NFKC, Compatibility Decomposition, followed by Canonical Composition):
    Ist wie "Form C", nur werden "compatibility character" durch richtige (aktuelle) Zeichen ersetzt. So wird z.B. anstelle aus dem einen Zeichen fi die zwei Einzelbuchstaben "fi" gemacht.
Bei allen diesen Normalisierungen ist zu beachten, dass es Fälle gibt in denen ein "Round-Trip" zur ursprünglichen Kodierung nicht mehr möglich ist. Auch kann sich die Bedeutung verändern (wenn z.B. das einzelne Zeichen bar durch die drei Einzelbuchstaben "b a r" ersetzt wird.

In der Windows-API kann eine Normalisierung in diese Formen mittels der Funktion FoldString durchgeführt werden.

Im .NET-Framework 2.0 hat die String-Klasse eine Methode um einen String zu normalisieren bzw. abzufragen ob der String normalisiert ist (siehe dazu auch meine Anfrage an Michael Kaplan von Microsoft).
Auch gibt es in dem Namespace System::Globalization sehr viele Klassen die sich mit Unicode und sonstigen Dingen der Globalisierung beschäftigen. Ebenso in dem Namespace System::Text, welche z.B. auch Klassen zur Konvertierung von Strings in diverse Zeichensätze anbietet.

Linux: Es gibt wohl diverse LIBs die das können (siehe Links weiter unten).


3.5.5 BiDi-Classes

Der Unicode-Standard definiert (hauptsächlich) wie die Strings im (Haupt-)Speicher kodiert werden. Dies wird auch als "logische Reihenfolge" bezeichnet. Diese Reihenfolge muss aber nicht unbedingt mit der Reihenfolge wie die Texte dargestellt werden identisch sein! In unsere Hemisphären kennen wir eigentlich nur die Links-Nach-Rechts (LTR) Schriftarten. Es gibt aber auch Sprachen, wo die Schreibweise von Rechts-Nach-Links (RTL) geht. Da Unicode alle Zeichen und Sprachen abdeckt (besser gesagt: "fast alle", da sich das Konsortium bis heute weigert "Klingonisch" aufzunehmen; aber die beiden Sprachen (Tengwar und Cirth) von J. R. R. Tolkien will man zukünftig aufnehmen), wurden natürlich auch solche Dinge berücksichtigt. Wenn nun RTL- und LTR-Texte gemischt werden, so redet man von bi-directional Text. Um auch hier eine einheitliche Vorgehensweise zu erlauben wurden dazu für jedes Zeichen bestimmte Properties aufgenommen (also wie das Zeichen bzgl. RTL/LTR dargestellt werden soll) und es wurde eine eigene Standard-Beschreibung definiert.

Für jedes Zeichen ist somit genau definiert, wie es sich in einem BiDi Umfeld verhalten soll. Dieses Verhalten kann mit expliziten Angaben von "Schreibrichtungen" verändert werden.

Das "durchgehen" solch eines Satzes von links nach rechts ist für uns etwas ungewohnt, da die Position des Carets ungewöhnlich "springt":
.

In dem obigen Beispiel werden die folgenden Codepoints verwendet und diese auch in dieser Reihenfolge in der Datei/Speicher abgelegt:
U+0044 U+0069 U+0065 U+0020 U+0648 U+064E U+0631 U+0652 U+062F U+064E U+0629 U+0020 U+0076 U+0065 U+0072 U+0077 U+0065 U+006C U+006B U+0074 U+002E
Die Reihenfolge auf dem Bildschirm ist aber eine etwas andere:
U+0044 U+0069 U+0065 U+0020 U+0629 U+064E U+062F U+0652 U+0631 U+064E U+0648 U+0020 U+0076 U+0065 U+0072 U+0077 U+0065 U+006C U+006B U+0074 U+002E

Hierzu noch eine kleine Quizfrage: Wenn das Caret mitten in einem RTL Text steht und man "Backspace" drückt, in welche Richtung bewegt sich das Caret und welches Zeichen wird gelöscht?

Den genauen Algorithmus und die (vielen) Besonderheiten beim Darstellen von BiDi-Text zu beschreiben würde den Rahmen dieses Dokumentes bei weitem Sprengen. Für detaillierte Infos, siehe den #TR9-Standard.

(PS: Es ist nicht so schlimm wenn man es nicht ganz verstanden hat, Microsoft hat es auch noch nicht ;) )

3.5.6 Textvergleich und Sortierung

Ein Textvergleich von Unicode-Strings ist eines der komplexesten, was es im Unicode Standard gibt (noch komplexer als BiDi!). Der (aktuelle) Textvergleich wird durch den "Unicode Technical Standard #10 (Unicode Collation Algorithm)" definiert. Ausgedruckt ergibt dies bei mir ca. 78 DIN A4 Seiten.

Ganz grob kann man sagen: Jedem Codepoint (Zeichen) ist ein (oder mehrere) Gewichtungsfaktor(en) zugeordnet. Anhand dieser Gewichte und anderen Kriterien (z.B. Ignorierung der Groß- und Kleinschreibung) wird ein "Sort Key" zusammengestellt, mit dem dann ein String verglichen werden kann. Diese Gewichtungsfaktoren sind entweder explizit für ein Zeichen definiert oder er kann aus der Position und sonstigen Eigenschaften berechnet werden.

Ein kleines Beispiel: Es soll der Sort-Key für den String "Öde" erstellt werden.
Hierzu muss man zuerst den String in die Normalform NFD bringen. Dies ergibt dann
U+004F U+0308 U+0064 U+0065.
Nun muss in der Tabelle für die Weights nachgeschaut werden und daraus ergibt sich das folgende "Collection Element Array":
Code:
(113B.0020.0008), (0000.0047.0002), (1010.0020.0002), (1029.0020.0002)

Nun hat man die Gewichte für alle 4 Zeichen. Dabei ist der erste Eintrag das Primäre Gewicht, der zweite das Sekundäre Gewicht und das dritte das Tertiäre Gewicht.
Aus diesem "Collection Element Array" wird nun der Sortkey gebildet, indem man die primäre, sekundäre und tertiäre Gewichte in der richtigen Reihenfolge anordnet (und man lässt die Zahlen weg, welche 0 sind). Daraus ergibt sich dann der Sortkey:
Code:
113B, 1010, 1029 | 0020, 0047, 0020, 0020 | 0008, 0002, 0002, 0002

Diese Zahlen kann man nun mit anderen Sortkeys mit einem "normalen" memcmp vergleichen. Wie man aber auch sehen kann, wird ein Sortkey sehr lang. Er braucht somit wesentlich mehr Platz als der String selbst. Deshalb gibt es auch hier Komprimierungen, welche den Sortkey wieder wesentlich reduzieren, damit das Vergleichen noch (Speicherplatz-)effizienter wird.

Auch hier kann ich aber aus Platzgründen nicht vollständig auf den Algorithmus eingehen. Eine genaue Erklärung gibt es wiederum im Unicode Technical Standard #10.

Trotzdem ein paar Links:In Windows kann z.B. mittels der Funktion LCMapString mit dem, Flag LCMAP_SORTKEY ein Sortkey für jeden beliebigen String erzeugt werden. Das gleiche gibt es für Linux in der ICU-Lib.


3.6 Beispiele mit Unicode

3.6.1 Gleiche Dateinamen unter Windows

Per Definition ist es unter Windows nicht möglich zwei Dateien mit dem gleichen Dateinamen im selben Verzeichnis anzulegen. Ein Versuch dies zu machen, wird scheitern. Mit dem Wissen von Unicode und dem Wissen, dass das Dateisystem eigentlich kein Unicode unterstützt, sondern die übergebenen (2-Byte langen) Zeichen einfach 1-zu-1 übernimmt, kann man jetzt Dateinamen erzeugen, die genau gleich angezeigt werden, aber unterschiedlich im Dateisystem gespeichert sind:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <windows.h>
#include <tchar.h>
 
int _tmain()
{
  // File names with same names!
  wchar_t name1[] = {'\\', 'B', 0xE4, 0x0};  // \Bä
  wchar_t name2[] = {'\\', 'B', 0x61, 0x0308, 0x0};  // \Bä
  HANDLE h1 = CreateFileW(name1, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
  HANDLE h2 = CreateFileW(name2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 
  CloseHandle(h2);
  CloseHandle(h1);
  return 0;
}


Hiermit hat man jetzt zwei Dateien, die "Bä" heißen (was eigentlich nicht sein sollte). Hätte der NTFS-Treiber eine korrekte Implementierung von Unicode, so würde er erkennen, dass beide Strings genau gleich sind und dies somit nicht zulassen.


3.6.2 Beispiele von speziellen Unicode-Zeichen

Ein ganz besonderes Unicode-Zeichen ist z.B. das SOFT HYPEN (U+00AD). Das ist ein Bindestrich, welcher nur manchmal sichtbar ist. Im Folgenden habe ich einen Text erstellt, welcher so viel wie möglich dieses Zeichen enthält. Wenn man also den Browser ganz eng zusammenschiebt, so sieht man, dass manchmal aus dem "nichts" ein Bindestrich zur Silbentrennung auftaucht (zumindest beim IE6).
Zitat:
<html>
<head>
</head>
</body>
Es wer­de ei­ne Fe­ste zwi­schen den Was­sern, die da schei­de zwi­schen den Was­sern.
Da mach­te Gott die Fe­ste und schi­ed das Was­ser un­ter der Fe­ste von dem Was­ser über der Fe­ste. Und es ge­schah so.
</body>
</html>
(das ganze gibt es auch hier zum separaten Anschauen)

3.7 Einschränkungen von C/C++
C/C++ kennt für Unicode den Datentyp wchar_t. Alle zeichenorientierten Unicode-Varianten der CRT nehmen diesen Datentyp entgegen. Bei den MS-Compilern ist dieser Datentyp 16-Bit lang. Dies hat zur Folge, dass aktuell keine Unterstützung für Unicode-Codepoints > U+FFFF vorhanden ist, da hierfür 32-Bit (oder 2x16-Bit) notwendig wären. So kann man z.B. mit iswdigit das Zeichen 'MATHEMATICAL MONOSPACE DIGIT FIVE' (U+1D7FB) nicht als Zahl erkennen. Hier wäre es notwendig, dass alle Unicode-Funktionen einen String entgegen nehmen würden und intern konsequent in UTF-8/16/32 kodiert würden. Bei einigen anderen Compilern ist wchar_t 32-Bit, womit eine vollständige Abbildung aller Unicode-Codepoints in einem wchar_t möglich ist.

3.8 Unterstützung von Unicode in .NET 2.0

.NET hat eine sehr vollständige Implementierung von Unicode. So werden auch die Codepoints > U+FFFF unterstützt. Strings werden intern als UTF-16 abgespeichert.

Mit dem folgenden Code kann man z.B. alle Unicode-Zeichen durchgehen und z.B. ermitteln, ob dieses eine Zahl ist und den Wert dazu ausgeben.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace System;
int main()
{
  for(int c=0; c<=0x10FFFF; c++)
  {
    if ((c >= 0x00d800) && (c <= 0x00dfff))
      continue;  // überspringe die Surrogates
    String ^s = Char::ConvertFromUtf32(c);
    if (Char::IsNumber(s, 0))
    {
      Console::WriteLine("'0x{0:X}' hat den Zahlenwert {1}",
        c, Char::GetNumericValue(s, 0));
    }
  }
}



4 Konvertierung zwischen unterschiedlichen Codepages

Möchte man Strings von einer Codepage in eine andere Codepage konvertieren, so ist der einfachste Weg, wenn man über Unicode geht.

Man konvertiert zuerst den Ausgangsstring nach Unicode (UTF-x Kodierung spielt dabei keine Rolle) und kodiert dann diesen Unicode-String in das Zielencoding. Unter Windows kann man hierzu MultiByteToWideChar und WideCharToMultiByte verwenden.
Ein Beispiel zur korrekten Verwendung der Funktionen ist im Folgenden:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <windows.h>
#include <tchar.h>
#include <string>
#include <assert.h>
 
DWORD ConvertUnicode2CP(const wchar_t *szText, std::string &resultString, UINT codePage = CP_ACP)
{
  resultString.clear();
  if (wcslen(szText) <= 0)
    return ERROR_SUCCESS;
  int iRes = WideCharToMultiByte(codePage, 0, szText, -1, NULL, 0, NULL, NULL);
  if (iRes <= 0)
    return GetLastError();
  char *szTemp = new char[iRes];
 
  iRes = WideCharToMultiByte(codePage, 0, szText, -1, szTemp, iRes, NULL, NULL);
  if (iRes <= 0)
  {
    delete [] szTemp;
    return GetLastError();
  }
 
  resultString = szTemp;
  delete [] szTemp;
  return ERROR_SUCCESS;
}
 
DWORD ConvertCP2Unicode(const char *szText, std::wstring &resultString, UINT codePage = CP_ACP)
{
  resultString.clear();
  if (strlen(szText) <= 0)
    return ERROR_SUCCESS;
  int iRes = MultiByteToWideChar(codePage, 0, szText, -1, NULL, 0);
  if (iRes <= 0)
    return GetLastError();
  wchar_t *szTemp = new wchar_t[iRes];
 
  iRes = MultiByteToWideChar(codePage, 0, szText, -1, szTemp, iRes);
  if (iRes <= 0)
  {
    delete [] szTemp;
    return GetLastError();
  }
 
  resultString = szTemp;
  delete [] szTemp;
  return ERROR_SUCCESS;
}
 
 
int _tmain(void)
{
  std::string a;
  std::wstring u;
 
  ConvertUnicode2CP(L"abcöäü", a, CP_ACP);
  // a => "abc" 0xf6 0xe4 0xfc
  ConvertCP2Unicode(a.c_str(), u, CP_ACP);
  assert(wcscmp(L"abcöäü", u.c_str()) == 0);
 
  ConvertUnicode2CP(L"abcöäü", a, CP_OEMCP);
  // a => "abc" 0x94 0x84 0x81
  ConvertCP2Unicode(a.c_str(), u, CP_OEMCP);
  assert(wcscmp(L"abcöäü", u.c_str()) == 0);
 
  ConvertUnicode2CP(L"abcöäü", a, CP_UTF8);
  // a => "abc" 0xc3 0xb6 0xc3 0xa4 0xc3 0xbc
  ConvertCP2Unicode(a.c_str(), u, CP_UTF8);
  assert(wcscmp(L"abcöäü", u.c_str()) == 0);
}



5 Anhang

5.1 Eingabe von Unicode-Zeichen

In Windows gibt es verschiedene Methoden Unicode-Zeichen einzugeben. Aus meiner Erfahrung gehen aber nicht alle Eingabemethoden in allen Programmen. Die universalste ist die Verwendung eines Unicode-IME (Input Methode Editors). Eine kurze Beschreibung wie man dies aktiviert hat der i18n Guru von Microsoft hier aufgeführt.


5.2 Links

5.2.1 Unicode allgemein

5.2.2 C/C++ Allgemein

5.2.3 Linux und Unicode

5.2.4 Unicode-Libraries für Linux

6 Nachwort

Die häufige Verwendung von Microsoft-Produkten rührt daher, dass ich diese hauptsächlich verwende. Ich habe mich bemüht aus dem Linux-Lager Infos zu finden, mir konnte dies aber nicht so gelingen wie erhofft. Für sachdienliche Hinweise bin ich jederzeit offen!

_________________
Greetings
Jochen
(Microsoft MVP VC++) My blog about Win32 and .NET: http://blog.kalmbach-software.de/ (deutsch)


Zuletzt bearbeitet von GPC am 12:41:12 23.11.2006, insgesamt 7-mal bearbeitet
SideWinder
Moderator

Benutzerprofil
Anmeldungsdatum: 19.10.2001
Beiträge: 18885
Beitrag SideWinder Moderator 18:44:43 12.10.2006   Titel:              Zitieren

Endlich ein Update zu Petzolds inzwischen sehr veraltetem Unicode-Kapitel :live::)

Lässt einem aber auhc hart auf den Boden fallen: Wann endlich ein Zeichensatz um sie alle zu knechten? :)

MfG SideWinder

_________________
http://www.dilbert.com/2009-06-11/
http://www.dilbert.com/2009-06-14/
Jochen Kalmbach
Moderator

Benutzerprofil
Anmeldungsdatum: 11.11.2005
Beiträge: 12132
Beitrag Jochen Kalmbach Moderator 22:58:47 12.10.2006   Titel:              Zitieren

SideWinder schrieb:
Lässt einem aber auhc hart auf den Boden fallen: Wann endlich ein Zeichensatz um sie alle zu knechten? :)
Ich muss sagen, ich verstehe den Kommentar nicht ganz :confused:
ADD: Ach so... so langsam verstehe ich den Kommentar... ich muss sagen, ich hab die Bücher nie gelesen :(

_________________
Greetings
Jochen
(Microsoft MVP VC++) My blog about Win32 and .NET: http://blog.kalmbach-software.de/ (deutsch)


Zuletzt bearbeitet von Jochen Kalmbach am 15:14:03 14.10.2006, insgesamt 1-mal bearbeitet
Gerhard W
Mitglied

Benutzerprofil
Anmeldungsdatum: 19.10.2006
Beiträge: 2
Beitrag Gerhard W Mitglied 10:34:32 19.10.2006   Titel:              Zitieren

Ich möchte ergänzen, dass die im Artikel erwähnte International Components for Unicode Library (ICU http://icu.sourceforge.net/]) auch ausgezeichnet unter Windows funktioniert.
Desweiteren werden in dieser Library auch folgende Themen behandelt:

Wenn man portablen Code schreibt, so ist der Einsatz dieser unter der "Open Source License" stehenden Komponente allemal empfehlenswert.

Gruesse,
Gerhard
goleo
Unregistrierter




Beitrag goleo Unregistrierter 06:18:31 31.10.2006   Titel:              Zitieren

kannst du zeigen wie man MultiByteToWideChar und WideCharToMultiByte richtig einsetzt? gibt es sonst noch wichtige konvertierungsfunktionen?
Jochen Kalmbach
Moderator

Benutzerprofil
Anmeldungsdatum: 11.11.2005
Beiträge: 12132
Beitrag Jochen Kalmbach Moderator 14:44:22 02.11.2006   Titel:              Zitieren

goleo schrieb:
kannst du zeigen wie man MultiByteToWideChar und WideCharToMultiByte richtig einsetzt?
Hab ein richtiges Beispiel dem Artikel hinzugefügt.
goleo schrieb:
gibt es sonst noch wichtige konvertierungsfunktionen?
Aus meiner Sicht nicht.
Ok, es gibt da noch die .NET-Funktionen zum Konvertieren (werde ich auch noch demnächst als Beispiele aufführen).
Es mag da noch im ICU Funktionen geben die ich mir aber noch nicht angeschaut habe.
Es gibt da dann auch noch die "mbtowc" und diverse "_mb*" Funktionen; ich würde sie aber nicht verwenden, da man nicht direkt eine locale (CP) angeben kann (nur bei der CRT8 geht dies mit dem "*_l" Funktionen. Diese greifen aber intern alle auf das MultiByteToWideChar (oder eben das Gegenteil) zurück. Kommt eben immer darauf an, ob man Platformunabhängig sein will (dann aber eher ICU).

_________________
Greetings
Jochen
(Microsoft MVP VC++) My blog about Win32 and .NET: http://blog.kalmbach-software.de/ (deutsch)
hustbaer
Mitglied

Benutzerprofil
Anmeldungsdatum: 27.10.2006
Beiträge: 18177
Beitrag hustbaer Mitglied 17:58:22 02.11.2006   Titel:              Zitieren

@Jochen Kalmbach:

Was sollte man (unter Windows) für MBCS -> wide-char Konvertierungen verwenden, wenn man diese dem File System füttern will? Also welche Flags? Bei MultiByteToWideChar steht ja in der MSDN default ist MB_PRECOMPOSED - ich nehme daher an Windows verwendet bei der internen, automatischen CP_ACP -> UTF-16 Konvertierung (wenn ich z.B. CreateFileA verwende) auch MB_PRECOMPOSED. Weisst du zufällig ob dem so ist? Und nur damit ich sicher bin, MB_PRECOMPOSED müsste mir dann Normalform "C" ausspucken, oder?

Cooler Artikel im übrigen, danke schön :)

_________________
"Let there be Licht..." http://lichttools.sourceforge.net/
Sehr cooles ASCII Spiel (leider nicht von mir): ASCII-Scramble - http://www.roskakori.at/ascii/
Jochen Kalmbach
Moderator

Benutzerprofil
Anmeldungsdatum: 11.11.2005
Beiträge: 12132
Beitrag Jochen Kalmbach Moderator 18:38:44 02.11.2006   Titel:              Zitieren

hustbaer schrieb:
wenn ich z.B. CreateFileA verwende
Das hängt ganz davon ab....
Du kannst es nämlich via API bestimmen, was verwendet werden soll:
Siehe:
- SetFileApisToANSI
- SetFileApisToOEM

_________________
Greetings
Jochen
(Microsoft MVP VC++) My blog about Win32 and .NET: http://blog.kalmbach-software.de/ (deutsch)
ss
Unregistrierter




Beitrag ss Unregistrierter 13:23:51 14.12.2006   Titel:              Zitieren

Schöner artikel nur du hast 2 UNICODE Codierungen vergessen:)
Und zwar UCS-2 und UCS-4.
Artchi
Autor

Benutzerprofil
Anmeldungsdatum: 16.03.2002
Beiträge: 8699
Beitrag Artchi Autor 13:36:10 14.12.2006   Titel:              Zitieren

UCS ist kein Unicode. Unicode ist zu UCS kompatibel. So rum wird ein Schuh draus.


Zuletzt bearbeitet von Artchi am 13:36:30 14.12.2006, insgesamt 1-mal bearbeitet
c++.de :: Die Artikel ::  Einführung in Codepages und Unicode  
Gehen Sie zu Seite 1, 2  Weiter
Auf Beitrag antworten

Zeige alle Beiträge auf einer Seite




Nächstes Thema anzeigen
Vorheriges Thema anzeigen
Sie können keine Beiträge in dieses Forum schreiben.
Sie können auf Beiträge in diesem Forum antworten.
Sie können Ihre Beiträge in diesem Forum nicht bearbeiten.
Sie können Ihre Beiträge in diesem Forum nicht löschen.
Sie können an Umfragen in diesem Forum nicht mitmachen.

Powered by phpBB © 2001, 2002 phpBB Group :: FI Theme

c++.de ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de Werbekostenerstattung verdient werden kann.

Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info und www.c-plusplus.net enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt (vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden. Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.