Idealerweise sollte ein Programm sowohl mit ISO-8859-1 als auch mit
UTF-8 Input-Dateien umgehen können, und zwar am besten so, dass das
Encoding nicht explizit angegeben werden muss.
Lässt sich dies realisieren?
Ja, indem das Programm sich den Inhalt der Datei "ansieht", entscheidet,
welches Encoding vorliegt und den Text entsprechend dekodiert.
Im Falle von Perl kann hierfür das Modul
Encode::Guess
genutzt werden. Es ist Teil des Perl-Core und damit in jeder
Perl-Installation enthalten. Es wird mit
geladen.
Wir nutzen die objektorientierte Schnittstelle des Moduls. Theoretisch
sollte folgender Sechszeiler die Aufgabe erledigen:
Encode::Guess->set_suspects('iso-8859-1');
my $dec = Encode::Guess->guess($text);
if (!ref $dec) {
die "ERROR: $dec\n";
}
$text = $dec->decode($text);
Erläuterung:
- Zeile 1:
-
Wir definieren iso-8859-1 als Encoding, das zusätzlich zu den
Default-Encodings ascii, utf8, UTF-16/32 mit BOM,
geprüft werden soll.
- Zeile 2:
-
Der Inhalt der Datei steht auf der Variable $text (das Einlesen hat
vorher stattgefunden und ist hier nicht wiedergegeben). Die Methode
guess() versucht das Encoding zu ermitteln und liefert im
Erfolgsfall ein passendes Decoder-Objekt. Im Fehlerfall liefert
die Methode eine Fehlermeldung (also eine Zeichenkette).
- Zeile 3-5:
-
Fehlerbehandlung. Falls wir eine Fehlermeldung erhalten, brechen
wir mit einer Exception ab. Zwei mögliche Fehlerfälle sind:
-
Der Text entspricht keinem der geprüften Encodings.
-
Mehr als eines der Encodings kommt infrage.
- Zeile 6:
-
Es wurde ein Decoder-Objekt geliefert, also ein Encoding
eindeutig erkannt. Wir dekodieren den Text mit dem Decoder-Objekt.
Leider funktioniert diese Implementierung nicht!
Denn wir stellen folgendes fest:
-
Ist die Datei ISO-8859-1 kodiert, gelingt das Dekodieren.
-
Ist die Datei UTF-8 kodiert, bricht der Code mit der Fehlermeldung
ERROR: utf8 or iso-8859-1
ab, d.h. der Methode guess() war es nicht möglich, das Encoding
eindeutig zu bestimmen.
Woran liegt das?
Die Ursache ist, dass jede UTF-8-Datei formal
auch eine ISO-8859-1-Datei ist. Denn jede Datei ist formal eine
ISO-8859-1-Datei, selbst eine Binärdatei wie z.B. ein JPEG-Bild. Das
liegt daran, dass ISO-8859-1 ein Ein-Byte-Encoding ist, bei dem alle
256 Werte belegt sind.
Es ist also fruchtlos und hinderlich, mit Encode::Guess auf
ISO-8859-1 testen zu wollen.
Ist die Unterscheidung von UTF-8 und ISO-8859-1 also nicht möglich?
Doch, sie ist möglich, wenn auch nicht mit den Mechanismen von
Encode::Guess allein. Denn auch wenn UTF-8 formal gültiges
ISO-8859-1 darstellt, gilt nicht die Umkehrung, dass jeder ISO-8859-1
Text valides UTF-8 darstellt. Es ist sogar sehr unwahrscheinlich,
dass ein realer ISO-8859-1 Text, gleichzeitig valides UTF-8 ergibt,
beinahe ebensowenig, wie dass ein ISO-8859-1 Text ein JPEG-Bild ergibt.
Unter Berücksichtigung dieser Tatsache können wir die Unterscheidung
von ISO-8859-1 und UTF-8 hinreichend sicher vornehmen:
my $dec = Encode::Guess->guess($text);
if (ref $dec) {
$text = $dec->decode($text);
}
elsif ($dec =~ /No appropriate encodings found/i) {
$text = Encode::decode('iso-8859-1',$text);
}
else {
die "ERROR: $dec\n";
}
Erläuterung:
- Zeile 1:
-
Der Dateiinhalt wird auf die Default-Encodings ascii, utf8,
UTF-16/32 mit BOM - also Unicode - geprüft.
- Zeile 2-4:
-
Falls UTF-8 erkannt wurde (oder eines der anderen Unicode-Encodings)
nutzen wir das gelieferte Encoder-Objekt um den Text zu dekodieren.
- Zeile 5-7:
-
Falls kein Unicode-Encoding erkannt wurde, muss es sich um eine ISO-8859-1
Datei handeln, denn andere Encodings erwarten wir nicht. Wir dekodieren
den Text ohne Encoder-Objekt (alternativ) mit der Funktion
Encode::decode().
- Zeile 8-10:
-
Falls ein sonstiger Fehler eingetreten ist, brechen wir mit
einer Exception ab.
Dieser Ansatz ("Wenn etwas nach UTF-8 aussieht, ist es auch UTF-8, sonst
betrachten wir es als ISO-8859-1") funktioniert.
Das Ganze als vollständige Implementierung einer Perl-Klasse File
mit einer einzelnen Methode decode():
package File;
use strict;
use warnings;
use Encode::Guess ();
# ---------------------------------------------------------------------------
=encoding utf8
=head1 NAME
File - Klasse mit Datei-Operationen
=head1 METHODS
=head2 decode() - Lies und dekodiere eine Textdatei
=head3 Synopsis
$text = $class->decode($file);
=head3 Description
Lies Textdatei $file und liefere den dekodierten Inhalt zurück.
Als Character Encoding erwarten wir Unicode (speziell UTF-8) oder
Latin1 (ISO-8859-1).
=cut
# ---------------------------------------------------------------------------
sub decode {
my ($class,$file) = @_;
# Datei einlesen
local $/ = undef;
open my $fh,'<',$file or die "ERROR: open failed: $file ($!)\n";
my $text = <$fh>;
close $fh;
# Encoding ermitteln und Text dekodieren
my $dec = Encode::Guess->guess($text);
if (ref $dec) {
# Wir dekodieren Unicode
$text = $dec->decode($text);
}
elsif ($dec =~ /No appropriate encodings found/i) {
# Erwarteter Fehler: Wir dekodieren Latin1
$text = Encode::decode('iso-8859-1',$text);
}
else {
# Unerwarteter Fehler
die "ERROR: $dec\n";
}
return $text;
}
# ---------------------------------------------------------------------------
=head1 AUTHOR
Frank Seitz, L<http://fseitz.de/>
=head1 LICENSE
This code is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
# ---------------------------------------------------------------------------
1;
# eof
Links