ubuntuusers.de

Perl und utf-8

Status: Ungelöst | Ubuntu-Version: Ubuntu 10.04 (Lucid Lynx)
Antworten |

drizzo

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Hallo Gemeinde,

Ich nutze einige Module um Mails abzurufen und möchte diese parsen, jedoch kommen die im iso-8859-1 an und ich möchte Sie in utf-8 haben....

[006] [Gelesen] =?iso-8859-1?B?3NbE?=
[007] =?iso-8859-1?Q?Umlaute_=E4=F6=FC=DF?=
Alles ab hier:

=E4=E4=E4
=F6=F6=F6
=FC=FC=FC
=DF=DF=DF

Wie wechsle ich jedoch auf utf-8, da ich den String weitergeben möchte als utf-8 ?

bisher blieb mir nor iconv:

1
$body = `echo '$body'|iconv -f iso-8859-1 -t utf-8 `;

Das kann ja nun nicht die Lösung sein. Hat jemand eine bessere Idee ? ☺

Gruß Drizzo

JuergenF

Anmeldungsdatum:
22. Oktober 2004

Beiträge: 2009

Wohnort: FFM

Geht das nicht mit dem Modul Encode?

drizzo

(Themenstarter)

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Habe ich versucht. Leider erfolglos. Oder ich habe mich schlecht angestellt ☹ Auch scheint "from_to" nicht mehr integriert zu sein.

Gruß Drizzo

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Doch das ist alles noch integriert. Du musst die Funktionen aber importieren, sonst stehen sie nicht zur Verfügung.

use Encode qw(encode decode from_to);

Ansonsten bist du zu Oberflächlich. Aussagen wie "Geht nicht" helfen keinen. UTF-8 richtig zu verwenden ist eigentlich nicht so schwer, aber du musst einige Dinge beachten. Von daher wäre es eigentlich gut wenn du ein Minimales Beispiel zeigen kannst das den Fehler reproduziert wo du meinst es wäre falsch.

Ansonsten welche Module nutzt du den um deine Mails abzurufen? Hast du geschaut ob man dort ein Encoding angeben kann?

drizzo

(Themenstarter)

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Content-type der Mail ist : iso-8859-1

 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
use utf8;         # Sourcecode ist UTF-8 Codiert gespeichert
use open ':utf8'; # Neue Eingabe/Ausgabehandles sind automatisch utf8 Codiert
use open ':std';  # STDIN, STDOUT, STDERR wird eingestellt wie mit dem open Pragma angegeben, 
                  # sprich in diesem Fall ebenfalls auf utf8 gestellt.
use Encode qw(encode decode from_to);


use Net::IMAP::Simple;
use Email::Simple;

    # open a connection to the IMAP server
    my $server = new Net::IMAP::Simple( 'server' );

    # login
    $server->login( 'user', 'pass' );

    # "inbox" auswählen 
    my $number_of_messages = $server->select('inbox');

    foreach my $msg (1..$number_of_messages ){
        my $es = Email::Simple->new(join '', @{ $server->get($msg) } );

        if ($server->seen( $msg )) { next; }

        printf("[%03d] %s\n", $msg, $es->header('Subject'));

        my $mailbody = $es->body;
        from_to($mailbody, "iso-8859-1", "utf8");

        my @mailbody = split /\n/ , $mailbody;

  }

Lasse ich mir den Mailbody ausgeben erhalte Ich statt Peter Lüstüg:

Name  : Peter L=FCst=FCg

Terminalenvironment:

1
2
3

~# env|grep -i lang
LANG=de_DE.UTF-8

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Also Net::IMAP::Simple beachtet die Kodierung wohl schon. Daher musst du nicht mehr manuell decodieren. Die zusätzlichen Aufrufe von encode() wo die Rückgabe von der get Methode nochmal decodierst sind also zu viel.

Solche Strings "=?iso-8859-1?Q?Umlaute_=E4=F6=FC=DF?=" haben aber nichts mehr mit der Zeichenkodierung zu tun. Das was du hier siehst, sind "Encoded Words". Solche Sachen kannst du mit dem Modul MIME::Words umcodieren.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env perl
# Core Modules
use strict;
use warnings;
use utf8;
use open ':encoding(UTF-8)';
use open ':std';
use Encode qw(decode);
use MIME::Words qw(decode_mimewords);

for my $decode ( decode_mimewords("=?iso-8859-1?Q?Umlaute_=E4=F6=FC=DF?=") ) {
    my ( $str, $encoding ) = @$decode;
    my $text = decode($encoding, $str);
    print $text, "\n";
}

drizzo

(Themenstarter)

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Hallo Sid,

vielen Dank für Deinen Lösungsansatz. Was aber ist, wenn ich wie jetzt einen gesamten Mailbody "$mailbody" "$subject" und noch weitere Variablen mit mehreren Wörtern nutze, welche nicht alle Mime codiert sind ?

Kann ich das ganze in einer Funktion auslagern und einfach den gesamten Text durchlaufen lassen, so dass ich danach einen "normalen" Fliesstext mit lesbaren Inhalten habe ?

Der Enctype müßte ja rein theoretisch nur einmal ermittelt werden je Text, oder?

lieben Gruß, drizzo

drizzo

(Themenstarter)

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Die Mapage schreibt dazu im Beispiel:

1
2
3
4
   $enc = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>';
    foreach (decode_mimewords($enc)) {
        print "", ($_[1] || 'US-ASCII'), ": ", $_[0], "\n";
    }

Mit der Bemerkung:

decode_mimewords ENCODED Function. Go through the string looking for RFC 2047-style "Q" (quoted-printable, sort of) or "B" (base64) encoding, and decode them.

In an array context, splits the ENCODED string into a list of decoded [DATA, CHARSET] pairs, and returns that list. Unencoded data are returned in a 1-element array [DATA], giving an effective CHARSET of undef.

Sollte also heissen, dass ich aus jedem Wort innerhalb vom String "$enc" ein dekodiertes Wort nach 'US-ASCII' zurückerhalte. Im falle von meinem Beispiel hat es leider nicht geklappt. Die Wörter waren weiter wie oben beschrieben encodiert.

Ich überprüfe nochmals.

Merci.

drizzo

(Themenstarter)

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Verständnis problem :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
        my $es = Email::Simple->new(join '', @{ $server->get($msg) } );

        # Subject aus Email Simple Objekt holen.
        my $header = $es->header('Subject');

        my @test = decode_mimewords($header);
        $header = $test[0];
        print Dumper $header;

        if ($server->seen( $msg )) {
                printf("[%03d] %s\n", $msg, "[Gelesen] " . $header);
                next;
        }


        printf("[%03d] %s\n", $msg, $header);

ergibt:

[

005] [Gelesen] ARRAY(0x1406fe8)
$VAR1 = [
          'ÜÖÄ',
          'iso-8859-1'
        ];
[006] [Gelesen] ARRAY(0x140ef80)
$VAR1 = [
          'Umlaute äöüß',
          'iso-8859-1'
        ];
[007] ARRAY(0x141b7d0)
$VAR1 = [
          'Testmail'
        ];

Was mach ich falsch, ich denke das erste Element [0] sollte doch der Header sein in richtiger Dekodierung ?

Danke, Gruß Drizzo

drizzo

(Themenstarter)

Anmeldungsdatum:
16. Oktober 2006

Beiträge: 68

Wohnort: Berlin

Also ich bin nun etwas Ratlos :

1
2
3
my $header = decode_mimewords($es->header('Subject'));
print Dumper $header;
printf("[%03d] %s\n", $msg, $header);

bei der Ausgabe erhalte ich :

$VAR1 = 'Umlaute äöüß';
[007] Umlaute ����

Nun bin ich noch mehr verwirrt ....

Warum ist per Dumper ausgegeben alles richtig ?

Sid_Burn

Anmeldungsdatum:
23. Oktober 2004

Beiträge: 2159

Was aber ist, wenn ich wie jetzt einen gesamten Mailbody "$mailbody" "$subject" und noch weitere Variablen mit mehreren Wörtern nutze, welche nicht alle Mime codiert sind ? Kann ich das ganze in einer Funktion auslagern und einfach den gesamten Text durchlaufen lassen, so dass ich danach einen "normalen" Fliesstext mit lesbaren Inhalten habe ?

Das MIME::Words ist speziell da, das du innerhalb des Textes mehrere Verschiedene Codierungen nutzen kannst. Und es kann sogar sein das nur einzelne Wörter Codiert sind. Es ist also bereits vorgesehen das nur teile Codiert sind.

Der Enctype müßte ja rein theoretisch nur einmal ermittelt werden je Text, oder?

Ne, wie bereits gesagt jedes Wort kann mit MIME::Words eine andere Codierung haben. Daher kannst diese nicht einmalig ermitteln.

Sollte also heissen, dass ich aus jedem Wort innerhalb vom String "$enc" ein dekodiertes Wort nach 'US-ASCII' zurückerhalte. Im falle von meinem Beispiel hat es leider nicht geklappt. Die Wörter waren weiter wie oben beschrieben encodiert.

Nein, du erhälst sozusagen eine Binär darstellung, und der zweite Wert sagt dir in welcher Codierung der String vorhanden ist. Wenn du das in die interne Perl darstellung umwandeln möchtest musst du nochmal selber decodieren. Das was MIME::Words macht ist eben die einzelnen Wörter oder beser gesagt Zeichen zum Beispiel Base64 codiert umzuwandeln so das du es Problemlos zum Beispiel über das SMTP Protokoll senden kannst.

Was mach ich falsch, ich denke das erste Element [0] sollte doch der Header sein in richtiger Dekodierung ?

Nein, der erste Wert ist der String in binärer darstellung. Der zweite Array Wert sagt in welcher Codierung dieser ist. Du musst mit decode() also diesen Part nochmals decodieren. Aber nur wenn auch der zweite Wert gesetzt ist. Wenn kein zweites Element im Array steht brauchst du nicht dekodieren.

Warum ist per Dumper ausgegeben alles richtig ?

Das hängt alles davon ab, ob du decodiert hast oder nicht. Wenn du einen binären String hast, und dieser liegt nicht in der UTF-8 Dekodierung vor, also sagen wir mal Latin-1 und du gibst etwas aus, dann wandelt Perl 5 den Wert immer vorher binär um. Wenn du

use open ':utf8';
use open ':std';

definiert hast dann wandelt er alles vorher in einer UTF-8 Sequenz um. Wenn deine Codierung nicht richtig gesetzt ist, kann es dann durchaus passieren das er dann doppelt en/decodiert. Data::Dumper gibt den Wert direkt ohne en/decodierung aus. Das kann dann richtig aussehen, muss aber nicht. In deinem Beispiel sieht das übrigens falsch ist. Wenn es UTF-8 ist und die dekodierung richtig gesetzt ist, würde Data::Dumper dort UTF-8 Codepoints anzeigen, was es nicht tut. Ein Indikator dafür das du ein UTF-8 String hast, aber nicht dekodiert hast. Daher Perl weiß nicht das es UTF-8 ist, und sieht es als einen Binärstring an. Und wenn du es mit Data::Dumper direkt ausgiebst wird der Binärstring direkt ausgegeben. Was dann auf nem UTF-8 Terminal auch korrekt "ausschaut".

Antworten |