Benne: Unicode

Hallo,

nachdem ich vor ein paar Tagen mein Linux System komplett auf UTF-8 umgestellt habe, bin ich gerade dabei die ersten Perl Skripte auf die neue Technologie anzupassen :D

Leider ist das im Allgemeinen eine sehr anstregende Arbeit und man wünscht sich doch im geheimen, dass die ganze Welt _sofort_ auf UTF-8 umstellt ;)

Bis es soweit ist kämpfe ich mit den Problemen weiter ;)
Momentan scheitere ich an zwei Hürden, die ich nur mit hässlichen Workarounds beheben kann.

Mein Perl Skript generiert eine XHTML Seite mit Formular. Die Werte werden anschließend an das gleiche Skript zugesendet und verarbeitet.
Zunächst habe ich darauf geachtet, dass dem Browser auch an allen Stellen mitgeteilt wird, dass es sich um ein UTF-8 Dokument handelt und er es entsprechend behandeln soll:

  
<?xml version="1.0" encoding="UTF-8" ?>  
...  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />  

sowie durch setzen des HTTP-Header Attributs:

  
Content-Type: text/html; charset=UTF-8  

Zubeachten ist im nächsten Schritt, dass ich das CGI Modul für die Parameterauswertung verwende.

  
use encoding 'utf8';  
use CGI;  
my $q = new CGI;  
print $q->param('Feld1'), "<br/>\n";  

Die Methode param gibt allerdings keinen UTF-8 String zurück, sondern einen byte-orientierten String.
Die Folge: aus "täst" wird "täst"

Das gleiche Problem habe ich auch bei meinem Datenbankzugriff über das <http://search.cpan.org/author/TIMB/DBI-1.51/DBI.pm@DBI Modul>.

Mein Workaround ist bisher folgender:

  
$str = "$str";  

Das Formularproblem löse ich durch setzen von accept-charset:

  
<form ... accept-charset="ISO-8859-1">  

.. was dann gleichzeitig die Weiterverarbeitung (schicken per Email) deutlich erleichtert. Das gefällt mir allerdings nicht.

Ich vermute allerdings hier eine gemeinsame Ursache, kann mir jemand weiter helfen?

Gruß,
Benne

--
ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
  1. Moin!

    Das Formularproblem löse ich durch setzen von accept-charset:

    <form ... accept-charset="ISO-8859-1">

      
    Das löst nichts, das verursacht die Probleme erst.  
      
    Wenn du UTF-8 verarbeiten willst, mußt du zwingend "UTF-8" als accept-charset definieren, sonst kriegst du nur Müll im falschen Encoding - und zusätzlich gehen noch Zeichen verloren, die mit ISO-8859-1 nicht codierbar sind.  
      
     - Sven Rautenberg
    
    -- 
    "Love your nation - respect the others."
    
    1. Hallo Sven,

      Das löst nichts, das verursacht die Probleme erst.
      Wenn du UTF-8 verarbeiten willst, mußt du zwingend "UTF-8" als accept-charset definieren, sonst kriegst du nur Müll im falschen Encoding - und zusätzlich gehen noch Zeichen verloren, die mit ISO-8859-1 nicht codierbar sind.

      doch, seltsamerweise schon. Wenn ich es weglasse oder auf "UTF-8" einstelle, dann werden die Umlaute eben zu "ä" und ähnlich hässlichen Zeichen.

      Gruß,
      Benne

      --
      ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
      1. Moin!

        doch, seltsamerweise schon. Wenn ich es weglasse oder auf "UTF-8" einstelle, dann werden die Umlaute eben zu "ä" und ähnlich hässlichen Zeichen.

        Wenn du dieses Doppelzeichen siehst, dann kriegst du UTF-8 geliefert und stellst es als ISO-8859-1 dar.

        Wenn du auf ISO-8859-1 umstellst, kriegst du ISO-codierte Zeichen, die mit dieser Codierung logischerweise auch ok aussehen.

        Also hast du nicht alles auf UTF-8 umgeschaltet. Tu das, und es wird funktionieren.

        - Sven Rautenberg

        --
        "Love your nation - respect the others."
        1. Hallo Sven,

          Also hast du nicht alles auf UTF-8 umgeschaltet. Tu das, und es wird funktionieren.

          hm, dann bin ich vielleicht gerade etwas blind ;)

          Ich habe nochmal versucht mein Problem auf das Wesentliche zu reduzieren.
          Das Beispiel (%C3%A4 entspricht einem 'ä')
          Der Code vom Perl Skript
          zu beachten ist hier folgendes: lasse ich in der 3. Zeile das
          use encoding 'utf8';
          weg, so bekomme ich in diesem Fall das gewünschte Ergebnis. In meinem komplexeren Fall führt das natürlich zu vielen Problemen bei der sonstigen utf Verarbeitung.

          Gruß,
          Benne

          --
          ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
          1. Moin!

            Ich habe nochmal versucht mein Problem auf das Wesentliche zu reduzieren.
            Das Beispiel (%C3%A4 entspricht einem 'ä')

            Funzt nicht in Opera 9: XML parsing error. Das <?xml... gehört in die erste Zeile, nicht in die zweite.

            zu beachten ist hier folgendes: lasse ich in der 3. Zeile das
            use encoding 'utf8';
            weg, so bekomme ich in diesem Fall das gewünschte Ergebnis. In meinem komplexeren Fall führt das natürlich zu vielen Problemen bei der sonstigen utf Verarbeitung.

            Welche Perl-Version benutzt du? Perl 5.6 hat mit Unicode massive Probleme, erst mit 5.8 geht das reibungslos. Zumindest, was die Multibyte-Stringverarbeitung angeht.

            - Sven Rautenberg

            --
            "Love your nation - respect the others."
            1. Hallo,

              Das Beispiel
              Funzt nicht in Opera 9: XML parsing error. Das <?xml... gehört in die erste Zeile, nicht in die zweite.

              ohja, da hab ich wohl noch einige Fehler drinnen gehabt. Sollte nun stimmen.

              Welche Perl-Version benutzt du? Perl 5.6 hat mit Unicode massive Probleme...

              jo, so steht es auch auf perldoc perlunicode
              meine Version ist 5.8.4

              Gruß,
              Benne

              --
              ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
              1. Moin!

                Das Beispiel
                Funzt nicht in Opera 9: XML parsing error. Das <?xml... gehört in die erste Zeile, nicht in die zweite.
                ohja, da hab ich wohl noch einige Fehler drinnen gehabt. Sollte nun stimmen.

                Irgendwas codiert bei dir UTF-8 doppelt. Im Quellcode stehen jedenfalls VIER Bytes drin, die die zwei Zeichen darstellen, die eigentlich das ä in UTF-8 bilden sollen.

                Wenn du dein Formular mehrfach abschickst, solltest du das bemerken: Der String wird immer länger.

                - Sven Rautenberg

                --
                "Love your nation - respect the others."
                1. Hallo,

                  Irgendwas codiert bei dir UTF-8 doppelt. Im Quellcode stehen jedenfalls VIER Bytes drin, die die zwei Zeichen darstellen, die eigentlich das ä in UTF-8 bilden sollen.

                  ja, jetzt fällt es mir auch auf. Aber leider komme ich jetzt einfach nicht weiter.
                  Soll es etwa die Lösung sein auf

                    
                  use encoding 'utf8';
                  

                  zu verzichten, oder könnte das ein Bug im CGI Modul sein?

                  Trotzdem schonmal vielen Dank, denn ich denke das Problem ist jetzt ganz gut eingegrenzt.

                  Gruß,
                  Benne

                  --
                  ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
                  1. Hell-O!

                    Soll es etwa die Lösung sein auf

                    use encoding 'utf8';

                    
                    > zu verzichten?  
                      
                    [Ja](http://perldoc.perl.org/encoding.html), es sei denn, du schreibst dein Script in UTF-8.  
                      
                    Siechfred
                    
                    -- 
                    Hier könnte Ihre Werbung stehen.  
                    [Musikgeschmack](http://siechfred.kennt-wayne.de/2006/7/musikgeschmack) || [Das Steuerblog](http://www.steuerwerkstatt.de/blog/)  || [RT 221 Erfurt-Altstadt i.V.](http://rt221.anaboe.net/) 
                    
                    1. Hallo Siechfred,

                      Ja, es sei denn, du schreibst dein Script in UTF-8.

                      ich schreibe mein Skript in UTF8.

                      Gruß,
                      Benne

                      --
                      ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
                      1. ich schreibe mein Skript in UTF8.

                        Sven schrieb:
                        Irgendwas codiert bei dir UTF-8 doppelt..

                        Genau das tut encoding 'utf8'.

                        Siechfred

                        --
                        Hier könnte Ihre Werbung stehen.
                        Musikgeschmack || Das Steuerblog  || RT 221 Erfurt-Altstadt i.V.
                        1. Hallo Siechfred,

                          Irgendwas codiert bei dir UTF-8 doppelt..
                          Genau das tut encoding 'utf8'.

                          Und wie würdest du vorgehen, dass es nicht doppelt kodiert wird?

                          Danke und Gruß,
                          Benne

                          --
                          ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
                          1. Moin!

                            Irgendwas codiert bei dir UTF-8 doppelt..
                            Genau das tut encoding 'utf8'.
                            Und wie würdest du vorgehen, dass es nicht doppelt kodiert wird?

                            Wie so häufig, gibt die Dokumentation hier Auskunft.

                            Zwei Seiten findet man zu diesem Thema:
                            1. use utf-8 - http://perldoc.perl.org/utf8.html
                            Zusammengefaßt: Diese Anweisung sagt Perl, dass der Skripttext in UTF-8 codiert ist. Dadurch werden die Multibyte-Zeichen im Skripttext als UTF-8-Zeichen betrachtet. Ansonsten hat diese Anweisung keine Auswirkungen, insbesondere nicht auf Ein- und Ausgabe.

                            2. use encoding - http://perldoc.perl.org/encoding.html
                            Zusammengefaßt: Macht dasselbe, wie "use utf-8", zusätzlich ändert es aber noch das Encodingverständnis von STDIN und STDOUT. Das ist offensichtlich zuviel - oder zuwenig, denn ist durchaus denkbar, dass Perl entweder zuviel UTF-8-codiert, oder dass ihm die Information fehlt, dass die Eingabedaten schon UTF-8 sind und nur noch als solches betrachtet werden müssen.

                            Mit anderen Worten: Entweder bringst du Perl bei, die Eingabedaten korrekt zu interpretieren, oder du bringst Perl bei, die korrekten Ausgabedaten nicht zu verfälschen.

                            Ich habe von Perl kaum Ahnung, insbesondere nicht, was das Unicode-Handling angeht. Ich kann nur einige Erfahrungen mit Browserexperimenten zu UTF-8 anbieten - und die sagen mir schlicht, dass eine korrekte Formularbehandlung durch Browser nur passiert, wenn man an allen denkbaren Stellen (HTTP-Header, Meta-Tag, accept-encoding-Attribut) den Browser unmißverständlich drauf hinweist, dass das Encoding "UTF-8" ist. Mit anderen Worten: Wenn es dann nicht klappt, hast du ein Problem im Skript - nicht im Formular oder Browser.

                            - Sven Rautenberg

                            --
                            "Love your nation - respect the others."
  2. Ihr wart alle auf der falschen Fährte. Kurzlösung voarab: In deinem CGI-Beispiel schreibst du nach STDOUT. Du musst entweder STDOUT binmoden oder deine Ausgabedaten durch encode() jagen, bevor du sie printest.

    Es folgen drei Sachverhalte zum Thema im Detail:

    1. Wenn du dein Programmquellcode in UTF-8 speicherst, *musst* du use utf8; verwenden. (Das war einfach.) Das Pragma utf8 hat für dich keinen weiteren Nutzen, es sei denn, du frickelst an den Innereien von Perl5 selber, und in diesem Falle bist du ja schlau genug und hast meine Erklärung nicht nötig. ;)

    2. Immer wenn du Daten empfängst, *musst* du sie dekodieren. Immer wenn du Daten wegschreibst, *musst* du sie kodieren. Dekodieren bedeutet die Umwandlung eines externen Formats nach Perls eigenem Format. Kodieren bedeutet die Umwandlung von Perls eigenem Format in einen externes Format. Nur weil du bisher damit weggekommen bist, es zu unterlassen (und alles hat funktioniert), heißt das nicht, dass du es auch in Zukunft tun kannst. Sorry für die Umstände.

    Im Regelfalle heißt das in der Praxis:
    a) Wenn du von Dateien, Sockets, Datenbanken und anderen Prozessen liest und schreibst, benutze eines der folgenden Konstrukte:

      
    # implizit über IO-Layer über Pragma  
    # http://perldoc.perl.org/open.html  
    use open ':utf8';  
      
    # implizit über IO-Layer über Dateidisziplin  
    # http://perldoc.perl.org/functions/open.html, grep nach "disciplines"  
    open my $fh, '<:utf8', 'filename' or die "could not open filename for reading: $!";  
    open my $fh, '>:utf8', 'filename' or die "could not open filename for writing: $!";  
      
    # implizit über IO-Layer über binmode()  
    # http://perldoc.perl.org/functions/binmode.html  
    open my $fh, '<', 'filename' or die "could not open filename for reading: $!";  
    open my $fh, '>', 'filename' or die "could not open filename for writing: $!";  
    binmode $fh => ':utf8';  
    
    

    Wenn du von STDIN liest (nacktes <> tut das) und wenn du nach STDOUT schreibst (print ohne Filehandle tut das):

      
    # implizit über IO-Layer über binmode()  
    binmode STDIN => ':utf8';  
    binmode STDOUT => ':utf8';  
    
    
      
    # explizit über Encode  
    # http://perldoc.perl.org/Encode.html  
    # http://perlmonks.org/?node_id=551676  
    use Encode qw(decode encode);  
    my $input = decode('UTF-8', $q->param('par'));  
    print encode('UTF-8', $html);  
    
    

    3. Du kannst HTML-Formulare nicht zwingen, ein bestimmtes Format zu liefern.

    http://search.cpan.org/~dankogai/Encode-2.18/lib/Encode/Supported.pod
    "it is beyond the power of words to describe the way HTML browsers encode non-ASCII form data"

    http://www.cl.cam.ac.uk/~mgk25/unicode.html#web
    "both standardization and implementation are still a huge mess here"

    Dieses Posting wurde ohne Zuhilfenahme von perl verfasst. Alle Codebeispiele sind ungetestet.

    Benne, wenn du das hier gelesen hast, bitte antworte kurz.

    1. Hallo 'Name',

      nun verwirrt mich zum Beispiel folgendes:

      warum
      use utf8;
      statt
      use encoding 'utf8';
      wie es eigentlich überall empfohlen wird?

      ich empfange von aussen Strings, die eigentlich schon in UTF-8 codiert sind muss diese aber zuvor decoden, sodass ich sie in meinem Programm benutzen kann?

      Bei massivem IO führt dieses ständige decoding doch zu hohen Performanceverlusten?
      Wenn ich eine Datenbankabfrage mache, und einen "arrayref" zurück bekomme, dann muss ich nun alle Strings decoden:

        
      for(@$t) {  
          my $a = $_;  
          for(0..length(@{$a})) {  
              $a->[$_] = decode("UTF-8", $a->[$_]);  
          }  
      }  
      
      

      Das ist hässlich und wahrscheinlich nicht performant. Wie würde man das lösen, denn ich müsste das ja nun an ganz vielen Stellen einbauen..

      Danke und Gruß,
      Benne

      --
      ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)
      1. warum
        use utf8;
        statt
        use encoding 'utf8';

        * Das erste Pragma gab es schon länger als das zweite, also bin ich es gewohnt, so zu schreiben.
        * Es ist kürzer.
        * Es gibt keinen funktionalen Unterschied. http://perldoc.perl.org/utf8.html, greppe nach works much the same
        * TIMTOWTDI.

        Kannst du mich auf ein paar solche Empfehlungen für meine Sammlung verlinken? Danke!

        ich empfange von aussen Strings, die eigentlich schon in UTF-8 codiert sind muss diese aber zuvor decoden, sodass ich sie in meinem Programm benutzen kann?

        Generell ja. Perls internes Format ist zwar in Wirklichkeit UTF-8, aber erst decodieren setzt das passende Flag in der Variable. Du kannst dir Variablenflags mit Devel::Peek anschauen. http://search.cpan.org/dist/Devel-Peek/Peek.pm

        Bei massivem IO führt dieses ständige decoding doch zu hohen Performanceverlusten?

        Der ehemalige Pumpking Nick Clark hat Performanceverbesserungen rund ums Thema UTF-8 eingebaut. Ich kann die Ankündigung ums Verrecken nicht mehr im Web oder perldelta finden, aber er hat beim GPW8 darüber geredet. http://www.perl-workshop.de/

        Kurz: Wenn du schon Daten im Format UTF-8 zuführst, ist das Decodieren eine sehr schnelle Operation.

        Du darfst gerne Benchmarken, um zu sehen, ob tatsächlich ein hoher Performanceverlust auftritt. http://search.cpan.org/dist/perl-5.8.8/lib/Benchmark.pm Sehr viel schneller wird's nicht mehr gehen, das Potenzial ist gut ausgereizt.

        (Beachte, dass du zuerst Profiling betreiben musst, bevor du blind Optimierung in Angriff nimmst. http://search.cpan.org/dist/Bundle-Devel/ Es lohnt nicht, einen 1%-Flaschenhals zu verbessern, wenn woanders ein 20%-Loch klafft.)

        Wenn ich eine Datenbankabfrage mache, und einen "arrayref" zurück bekomme, dann muss ich nun alle Strings decoden:

        for(@$t) {

        my $a = $;
            for(0..length(@{$a})) {
                $a->[$
        ] = decode("UTF-8", $a->[$_]);
            }
        }

        
        > Das ist hässlich  
        
        Da stimme ich dir zu. Jeder Code, der nicht unmittelbar mit dem Ziel zu tun hat, wird vom Programmierer so empfunden. Das gleiche gilt für Untainting.  
          
        Leider sind diese hässlichen Sachen notwendig, weil Perl uns hier (derzeit?) kein DWIMmery oder Syntaxzucker anbieten kann. Wenn du ein langes Gesicht ob der Vorstellung machst, denk immer an die armen Javaprogrammierer, die viel lästigere Syntax ertragen müssen.  
          
        
        > und wahrscheinlich nicht performant.  
        
        Siehe Bemerkungen oben. Spekulieren kann jeder. Die manchmal verblüffende Wahrheit fördern nur die Werkzeuge zu Tage.  
          
        
        > Wie würde man das lösen, denn ich müsste das ja nun an ganz vielen Stellen einbauen..  
        
        1\. Wo nur möglich, benutze die impliziten Konstrukte. Die halten deinen Code sauber.  
        2\. Stelle deine Datenbank auf UTF-8 um und sage das auch deinem DBD-Treiber. Ich habe mit DBD::Pg hier vorbildlich gute Erfahrungen gemacht. <http://search.cpan.org/dist/DBD-Pg/Pg.pm#Database_Handle_Attributes>  
        3\. Wenn du explizite Decodierung benötigst, verzögere den Schritt so lang wie möglich, bevor du Manipulation an den Daten vornehmen musst. Eventuell fallen bei gewissen Umständen einige Datensätze unter den Tisch, und du sparst dir dann den Decodierungsschritt.  
        Hier ist es sehr hilfreich, ein passendes Variablennamensschema einzusetzen, damit du nicht durcheinanderkommst, ob die Daten schon decodiert sind oder nicht. Lies dazu <http://www.joelonsoftware.com/articles/Wrong.html>
        
        1. Hallo 'Name',

          vielen Dank für deine ausgezeichnete und umfassende Antwort. Ich denke ich komme mit den ganzen Ratschlägen weiter.

          Gruß,
          Benne

          --
          ie:% fl:( br:> va:) ls:> fo:| rl:° ss:) de:[ js:| ch:| mo:} zu:)