decode() nimmt einen String entgegen und Codiert ihn in Perls internes Format. Das eigentliche Format ist aber kein besonderes Format, sondern es ist simpel einfach nur UTF-8. Das dort nicht UTF-8 steht hat mehrere Gründe. Den für den eigentlichen Nutzer ist es irrelevant wie Perl ihn genau Codiert, der Benutzer/Programmierer soll eigentlich nur daran denken das es Unicode ist, mehr nicht, und man soll sich auch nicht darauf verlassen das es UTF-8 ist.
Wenn etwas also im Internen Format ist, kann Perl es verstehen. Wenn du damit allerdiengs I/O betreibst, dann musst du bei einer übergabe sagen in welche Zielcodierung er es wieder encodieren soll.
Durch dieses Encodieren oder eben encode() würde er es vom Internen Format zum Zielformat umwandeln. Wenn die Ausgabe auch UTF-8 sein soll müsste eigentlich nichts mehr gemacht werden. Aber da man sich ja nicht darauf verlassen soll das es UTF-8 ist, wird jeglicher String der im Internen Format vorliegt vor seiner Ausgabe automatisch encodiert.
Und das Default Encoding was er hier auswählt ist ISO-8859-1
Für ein besseres beispiel und um das Verständnis zu erhöhen kann ich ein paar Beispiele zeigen. Wichtig ist es zu erfahren wie Perl eine Variable ansieht, dafür kann man sehr gut Devel::Peek nutzen das die C Struktur einer Variablen ausgibt.
Code:
#!/usr/bin/perl
# Core Module
use strict;
use warnings;
use utf8;
use Encode qw(encode decode is_utf8);
use Devel::Peek;
my $str = "hälö";
Dump($str);
print "[$str]", length($str), "\n";
$str = encode("UTF-8", $str);
Dump($str);
print "[$str]", length($str), "\n";
Zur Umgebung sei gesagt das mein Editor die Datei als UTF-8 Speichert, sowie Terminal ebenfalls alles UTF-8 ist, also ein komplettes UTF-8 System.
Nun zur ausgabe:
Code:
sidburn@sid:~/perl$ ./utf8.pl
SV = PV(0x87e06d0) at 0x87fc878
REFCNT = 1
FLAGS = (PADMY,POK,pPOK,UTF8)
PV = 0x87f7dd0 "h\303\244l\303\266"\0 [UTF8 "h\x{e4}l\x{f6}"]
CUR = 6
LEN = 8
[h�l�] 4
SV = PVMG(0x8843278) at 0x87fc878
REFCNT = 1
FLAGS = (PADMY,SMG,POK,pPOK)
IV = 0
NV = 0
PV = 0x8877c70 "h\303\244l\303\266"\0
CUR = 6
LEN = 8
MAGIC = 0x88144f8
MG_VIRTUAL = &PL_vtbl_utf8
MG_TYPE = PERL_MAGIC_utf8(w)
MG_LEN = -1
[hälö]6
Wenn man die Dumps erstmal weg lässt dann siehst man das die erste Ausgabe falsch ist, allerdiengs hat er bei der Länge korrekt "4" angegeben. Sprich es waren 4 Zeichen. Bytes waren es mehr, nämlich 6.
Bei der zweiten Ausgabe sieht man die Ausgabe korrekt, allerdiengs gibt uns length() 6 zurück. Es betrachtet also nicht die Zeichen sondern wirklich die Bytes.
Bei den Dumps ist eigentlich erstmal nur FLAGS und PV Interessant. Bei Flags stehen eben mackierungen die eine Variable besitzt, und in PV steht der Inhalt.
Beim ersten Dump sehen wir hierbei das die Variable das "UTF8" Flag besitzt. Daher es ist im Internen Format von Perl codiert. Beim PV sehen wir letztendlich nochmals die Interne Darstellung, einmal in Oktaler Notation "\0303\0244" und einmal mit "\x{e4}". Letztendlich bedeutet beides das gleiche. Die zweite Notation ist nur Perls Form direkt ein Unicode Zeichen einzufügen, unabhägig von seiner Codierung. Die Oktale Form kann sich ja unterscheiden. z.B. wenn Perl sich entscheided UTF-16 als Internes Format zu nehmen.
Trotzdem ist die erste Ausgabe Falsch. Warum? Weil Perl eben vor jeder Ausgabe automatisch vom Internen Format ins ISO-8859-1 Format umwandelt. Das Interne Format wird durch die Flags gekennzeichnet. Den bei unserem ersten Dump steht "UTF8" noch als Flag. Was passiert nun bei einer Umwandlung? Wenn man einen String der bereits UTF8 gelfagt ist nach ISO-8859-1 codiert kommt dann danach folgender Dump heraus:
Code:
SV = PVMG(0x9019278) at 0x8fd2878
REFCNT = 1
FLAGS = (PADMY,SMG,POK,pPOK)
IV = 0
NV = 0
PV = 0x904c660 "h\344l\366"\0
CUR = 4
LEN = 8
MAGIC = 0x8fea4f8
MG_VIRTUAL = &PL_vtbl_utf8
MG_TYPE = PERL_MAGIC_utf8(w)
MG_LEN = -1
Wie man also sieht ist das Ergebnis nur noch 4 Bytes groß. Die "ä" und "ö" sind zu einem oktalen zeichen geworden. \344 ist Oktal und ist Dezimal = 288, Hex = E4.
Schaust du auf der ISO-8859-1 Tabelle nach http://en.wikipedia.org/wiki/ISO_8859-1 sieht man das dies das Zeichen "ä" in iso-8859-1 Kodierung ist.
Um es also nachzuvollziehen. mit decode() wandeln wir es ins Interne Format um. Perl kennt danach praktisch die Zeichenbedeutung. Und weiß das es ein "ä" ist. Das Interne Format wird mit UTF-8 dargestellt, obwohl das eigentlich irrelevant ist. Und bei einem encode() geben wir an in welchen zeichencodierung er unseren String codieren soll. Default ist es ISO-8859-1 also nimmt er die Repräsentation des zeichens "ä" im ISO-8859-1 Format vor was dann zum Resultat \344 wird.
Wenn man das Terminal übrigens auf ISO-8859-1 schaltet würde man korrekt "hälö" sehen.
Jetzt aber wieder zurück zum anfang, im Code hatte ich ja mit
Code:
$str = encode("UTF-8", $str);
den String Codiert, und ausgegeben und die Ausgabe war korrekt. Wenn du dir nun den PV anschaust und die Oktale darstellung wirst du feststellen dass sich letztendlich überhaupt nichts geändert hat. Es war schon UTF-8 codiert und ist auch so geblieben. Allerdiengs ist das Flag "UTF8" bei den Flags verschwunden.
Daher sieht Perl nun das es nicht mehr im Internen Format ist, und bei einer Ausgabe wird nicht mehr automatisiert ein encode() gemacht.
Module wie Tk oder generell C oder XS basierte Module haben aber noch ein weiteres Problem. Den viele behandeln das UTF8 Flag nicht. Daher jedes Modul das irgendeine Ausgabe macht ohne das Flag zu beachten codiert letztendlich wieder nach ISO-8859-1. Weiterhin gibt es auch Module die einfach die Oktale darstellung nutzen und es generell immer als UTF-8 ansehen oder nicht. DBD::mysql sieht z.B. Strings immer als in UTF-8 Kodiert an wenn man bei der verbindung das Flag "mysql_enable_utf8 => 1" setzt. Bzw DBD::mysql aktiviert auch immer das UTF8 Flag unabhängig davon ob die Daten wirklich UTF-8 Kodiert sind oder nicht. Genauso kann es sein das manche Module doppelt decodieren da sie annehmen sie bekommen kein UTF-8 geflagt String etc.
Zwar kann man absolut alle solche Probleme mit einem utf8::is_utf8($str) und einer if abfrage überprüfen und nur je nach rückgabe (en|de)codieren aber anscheind ist das wohl den meisten zu komplex. Bzw da viele Module von Amerikanern kommen und die sich meist einen Dreck um Unicode kümmern (brauchen sie ja nicht) wird soetwas meist auch nicht berücksichtigt. Daher du musst dich dann selber darum kümmern keine UTF-8 geflagten Strings zu übergeben und vorher immer alles "encodieren". Tk wird wohl das gleiche Problem haben.
Da ich "Tk" noch nie genutzt habe kann ich dir nicht sagen ob Tk vernünftig UTF-8 bzw überhaupt irgendwelche Codierungen beachten. Wenn es das tun würde, und der Modulautor es korrekt machen möchte, so müsste er eigentlich eine Option einbauen wo man die Codierung angeben muss.
Hat er das nirgendswo dann geht er entweder davon aus das er nur ungeflagte UTF-8 Strings bekommt oder Codiert bei einer ausgabe einfach immer oder ähnliches was letztendlich zu Fehlern führen kann.
Zum Schluß. Wichtig ist die Funktionalität der Schalter zu verstehen.
Wenn du beispielsweise "use utf8;" aktivierst dann sagst du Perl damit das der Sourcecode in einer UTF-8 Kodierung gespeichert ist. Daher beim Programmstart wandelt Perl alle variablen automatisch ins UTF-8 Format um. Bzw würde das UTF8 Flag aktivieren. Lässt du "use utf8" weg so wird die Ausgabe auch auf einen UTF8 Terminal richtig sein ohne ein encode() zu machen. Allerdiengs sieht er dann nicht mehr ein "ä" sondern zwei aufeinanderfolgende Zeichen aus der ISO-8859-1 Kodierung.
Das führt dazu das length() zum Beispiel nicht die anzahl der Zeichen sondern die Bytes zurück gibt. bei "hälö" also 6 anstatt 4. Auch können Regexe oder andere String Manipulationen zu Fehlern führen.
Was du nicht mit "use utf8" z.B. nicht sagst ist welche Codierung er bei open() nutzen soll. Wie die STD* Handles zu behandeln sind etc. Um das verhalten zu überschreiben gibt es aber die "use open ..." Pragma, was ich im Post davor schon erklärt hatte.
Lesezeichen