Ankündigung

Einklappen
Keine Ankündigung bisher.

mb_detect_encoding - Gibt es in PHP einen zuverlässigeren Weg, Encoding zu ermitteln?

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • mb_detect_encoding - Gibt es in PHP einen zuverlässigeren Weg, Encoding zu ermitteln?

    Hallo zusammen,

    ich habe mich jetzt recht lange damit befasst, wie man in PHP zuverlässig Charset-Detection umsetzen kann... leider erfolglos. Zunächst mal zu meinen Recherchen:
    • Charset-Detection ist eigentlich ein unlösbares Problem, das ist mir bekannt - es gibt aber Ansätze, die es relativ zuverlässig über Heuristik möglich machen
    • mb_detect_encoding gehört nach meinen Experimenten nicht zu diesen "zuverlässigen" Ansätzen, da er in vielen Fällen false oder ein falsches Encoding zu Tage fördert (selbst mit PHP 7.3)
    • Ein Beispiel für Charset-Detection, das "gut" funktoniert, ist https://github.com/Joungkyun/libchardet
    Nun habe ich bei Packagist mal einige libraries durchprobiert, war aber wenig erfolgreich. Einen "echten" Port von libchardet, oder eine Alternative wie z.B. für Python (https://pypi.org/project/chardet/) gibt es meiner Kenntnis nach für PHP nicht.

    Meine Anforderungen:
    • Ich möchte so zuverlässig wie möglich das verwendete Encoding / Charset einer Textdatei ermitteln können
    • Ich möchte nicht, dass man dabei auf einige wenige Charsets begrenzen muss, die es vielleicht sein könnten
    • Es muss eine native-PHP library oder eine gebräuchliche extension sein, nicht irgendwas abgefahrenes wie eine eigene Extension, ein Konsolen-Wrapper oder ein COM-Objekt-Mapper

    Kennt jemand da eine Möglichkeit? Bzw. was ist das zuverlässigste, was in PHP geht?
    Tutorials zum Thema Technik:
    https://pilabor.com
    https://www.fynder.de

  • #2
    Zitat von Andreas Beitrag anzeigen
    Hallo zusammen,

    . Einen "echten" Port von libchardet, oder eine Alternative wie z.B. für Python (https://pypi.org/project/chardet/) gibt es meiner Kenntnis nach für PHP nicht.

    Pro Tip: Documente durchscrollen:
    https://github.com/OOPS-ORG-PHP/mod_chardet

    Kommentar


    • #3
      tomBuilder Danke, die kenne ich... aber leider kann ich umgebungsbedingt nicht beliebige extensions aktivieren / voraussetzen:

      Es muss eine native-PHP library oder eine gebräuchliche extension sein, nicht irgendwas abgefahrenes wie eine eigene Extension
      mb_string ist aktiv, einige andere auch, aber ich dachte, dass es doch irgendwas in PHP geben muss, was halbwegs zuverlässig funktioniert ohne eine extension selbst kompilieren zu müssen...
      Tutorials zum Thema Technik:
      https://pilabor.com
      https://www.fynder.de

      Kommentar


      • #4
        Zitat von Andreas Beitrag anzeigen
        Hallo zusammen,

        ich habe mich jetzt recht lange damit befasst, wie man in PHP zuverlässig Charset-Detection umsetzen kann
        Ich auch einige Jahre schon.

        Zitat von Andreas Beitrag anzeigen
        [*]Charset-Detection ist eigentlich ein unlösbares Problem
        Richtig. Es bleibt fast immer ein erraten.

        Zitat von Andreas Beitrag anzeigen
        Kennt jemand da eine Möglichkeit? Bzw. was ist das zuverlässigste, was in PHP geht?
        Ich kann dir nur anbieten meinen Versuch anzuschauen und/oder zu probieren. Hab es in meine Debug-Klasse eingebaut, dort die Funktion detect_encoding().

        LG jspit





        Kommentar


        • #5
          jspit Danke. Der Versuch ist schon mal näher dran an dem was ich suche, aber ich sprach tatsächlich von einer allgemeineren Funktionsweise... deine Funktion kann tatsächlich nur 3 Charsets erkennen (+3 Varianten von UTF-8 - die Bezeichnung UTF-8mb4 ist aber meiner Kenntnis nach nicht spezifiziert und eher eine eigene Kreation richtig?). Folgendes habe ich bisher herausgefunden:

          Unzuverlässige allgemeine charset-Erkennung.
          PHP-Code:
          // alle parameterkombinationen liefern unzuverlässige ergebnisse
          $charset mb_detect_encoding($string);
          $charset mb_detect_encoding($stringnulltrue);
          $charset mb_detect_encoding($string, ["utf-8""iso-8859-1""windows-1252"], false); 
          Zuverlässige UTF-8-Erkennung:
          PHP-Code:
          if(preg_match("//u"$string)) {
              echo 
          "string ist utf-8";

          Quelle: https://www.php.net/manual/de/functi...ing.php#112391


          Wenn man nur UTF-8 als Ergebnis braucht, kann man folgendes versuchen:
          PHP-Code:
          if(!mb_check_encoding($output'UTF-8')     OR !($output === mb_convert_encoding(mb_convert_encoding($output'UTF-32''UTF-8' ), 'UTF-8''UTF-32'))) {    
              
          $output mb_convert_encoding($content'UTF-8''pass');  

          Quelle: https://stackoverflow.com/questions/...ncoding-in-php

          Eine ganz brauchbare (aber langsame) allgemeine Erkennung (liefert nach meinen Tests deutlich bessere Ergebnisse, als mb_detect_encoding):
          Hier fehlen aber einige Charsets, die in der Spezifikation enthalten sind, siehe dazu: https://html.spec.whatwg.org/multipa...acter-encoding
          PHP-Code:
          function detectEncoding ($string$enc=null$ret=null) {
                  static 
          $enclist = array(
                      
          'UTF-8''ASCII',
                      
          'ISO-8859-1''ISO-8859-2''ISO-8859-3''ISO-8859-4''ISO-8859-5',
                      
          'ISO-8859-6''ISO-8859-7''ISO-8859-8''ISO-8859-9''ISO-8859-10',
                      
          'ISO-8859-13''ISO-8859-14''ISO-8859-15''ISO-8859-16',
                      
          'Windows-1251''Windows-1252''Windows-1254',
                      );

                  
          $result false;

                  foreach (
          $enclist as $item) {
                      
          $sample iconv($item$item$string);
                      if (
          md5($sample) == md5($string)) {
                          if (
          $ret === NULL) { $result $item; } else { $result true; }
                          break;
                      }
                  }

              return 
          $result;

          Quelle: https://www.php.net/manual/de/functi...ing.php#113983

          Die PHP-Extension habe ich noch nicht ausprobiert, aber ich werde wohl nicht drumherum kommen, das als "beste Lösung wenn verfügbar" zu implementieren...
          Tutorials zum Thema Technik:
          https://pilabor.com
          https://www.fynder.de

          Kommentar


          • #6
            Der Unterschied zwischen ISO-8859-1, ISO-8859-2, usw. lässt sich nicht detektieren. Egal was du probierst, du wirst keine sinnvolle Lösung finden. Würdest du es ausprobieren, würdest du sehr schnell sehen, dass da nur Blödsinnergebnisse raus kommen.

            Es gibt nur eine Lösung: Sorge dafür, dass bei Textdaten das Encoding immer mitgeliefert wird. Also das Problem musst du nicht auf der technischen Ebene, sondern auf der organisatorischen Ebene lösen. Dort wo die Ursache ist.

            Kommentar


            • #7
              hellbringer
              Mir ist bekannt, dass iso-8859-1 und iso-8859-2 nur eine verschiedene Interpretation des gleichen Bytestreams sind, jedoch wäre halte ich es momentan folgendes den für meine Zwecke effektivsten Ansatz:
              • Ist das Charset UTF-8, braucht man nichts weiter zu tun (Prüfung mit preg_match("//u", $string))
              • Wenn nicht, verwendet man folgende Library, um die Sprache des Textes zu ermitteln (NICHT den Zeichensatz):
                https://github.com/patrickschur/language-detection - hier wäre auch eine Interpretation des gleichen Bytestreams möglich, da eine Sprache detektiert wird
              • Dann verwendet man folgende Referenztabelle (siehe https://html.spec.whatwg.org/multipa...acter-encoding), um den wahrscheinlichsten Zeichensatz anhand der Sprache zu ermitteln.


              Was meint ihr dazu?
              Tutorials zum Thema Technik:
              https://pilabor.com
              https://www.fynder.de

              Kommentar


              • #8
                hellbringer Mir ist bekannt, dass ISO-8859-1, ISO-8859-2 nur eine verschiedene Interpretation des gleichen Bytestreams ist, der für verschiedene Sprachen / Sprachgruppen dient... genau aus diesem Grund halte ich folgenden Ansatz für den für meine Zwecke effektivsten:Was haltet ihr davon?
                Tutorials zum Thema Technik:
                https://pilabor.com
                https://www.fynder.de

                Kommentar


                • #9
                  Ich frage mich wie die Library eine Sprache erkennen will ohne die Kodierung zu wissen. Denn ohne die Kodierung zu kennen, kommt ja oft nur Zeichenmüll raus. Und über diesen Müll lässt man dann einen Sprachendetektor laufen und er soll wie durch Zauberei da irgendwelche Wortfragmente erkennen? Also ich habe da meine Zweifel.

                  Und wie gehst du mit mehrsprachigen Texten um?

                  Wie gehst du mit Daten um, die keine "normalen" Text enthalten? Zum Beispiel Postadressen?

                  Meiner Ansicht nach wäre Schritt 1 die Kodierung herauszufinden. Erst dann, wenn die Kodierung bekannt ist, kann die Sprache als Schritt 2 erkannt werden. Da du aber die Sprache für Schritt 1 brauchst, beißt sich der Hund in den Schwanz.

                  Kommentar


                  • #10
                    Und wie gehst du mit mehrsprachigen Texten um?
                    Da hast du natürlich recht... ich werds mal ausprobieren, aber das könnte in der Tat schwierig werden...

                    Und wie gehst du mit mehrsprachigen Texten um?
                    Das kann die library wohl... sie gibt eine confidence pro Sprache zurück - ich muss es aber tatsächlich erst ausprobieren.

                    Vermutlich werde ich tatsächlich diese extension nutzen müssen und hoffen, dass die taugt oder einen eigenen Port von diesem chardet schreiben (was mir doch schon etwas viel Aufwand wäre, für mein Problem)... muss ich wohl einen Parameter "--charset" in meinProgramm einbauen...
                    Tutorials zum Thema Technik:
                    https://pilabor.com
                    https://www.fynder.de

                    Kommentar


                    • #11
                      Zitat von Andreas Beitrag anzeigen
                      Meine Anforderungen:
                      • Ich möchte so zuverlässig wie möglich das verwendete Encoding / Charset einer Textdatei ermitteln können
                      • Ich möchte nicht, dass man dabei auf einige wenige Charsets begrenzen muss, die es vielleicht sein könnten
                      • Es muss eine native-PHP library oder eine gebräuchliche extension sein, nicht irgendwas abgefahrenes wie eine eigene Extension, ein Konsolen-Wrapper oder ein COM-Objekt-Mapper
                      Diese Anforderungen sind nicht machbar, hellbringer hat das schon kommentiert, dem stimme ich zu.
                      Die Charsets auf einige wenige gängige zu begrenzen ist m.E. die einzige Möglichkeit, beim Raten eine hohe Trefferwahrscheinlichkeit zu erzielen.

                      Diese Funktion soll eine ganz brauchbare Erkennung liefern.
                      PHP-Code:
                      function detectEncoding ($string$enc=null$ret=null) {
                              static 
                      $enclist = array(
                                  
                      'UTF-8''ASCII',
                                  
                      'ISO-8859-1''ISO-8859-2''ISO-8859-3''ISO-8859-4''ISO-8859-5',
                                  
                      'ISO-8859-6''ISO-8859-7''ISO-8859-8''ISO-8859-9''ISO-8859-10',
                                  
                      'ISO-8859-13''ISO-8859-14''ISO-8859-15''ISO-8859-16',
                                  
                      'Windows-1251''Windows-1252''Windows-1254',
                                  );

                              
                      $result false;

                              foreach (
                      $enclist as $item) {
                                  
                      $sample iconv($item$item$string);
                                  if (
                      md5($sample) == md5($string)) {
                                      if (
                      $ret === NULL) { $result $item; } else { $result true; }
                                      break;
                                  }
                              }

                          return 
                      $result;

                      Mein kurzer Test zeigt das Gegenteil:
                      PHP-Code:
                      $strings = [
                        
                      "Ein ASCII string",
                        
                      "Text mit UTF8-Umlauten wie ä ",
                        
                      mb_convert_encoding("Text mit UTF8-Umlauten wie ä ","ISO-8859-1","UTF8"),
                        
                      mb_convert_encoding("Text mit ä und € ","CP1252","UTF8"),
                      ];
                      $results = [];
                      foreach(
                      $strings as $string){
                        
                      $results[] = ["string" => $string,
                        
                      "detectEncoding"  =>  detectEncoding($string),
                        
                      "debugClass" => debug::detect_encoding($string)
                        ];
                      }
                      debug::write($results); 
                      Resultat:
                      PHP-Code:
                      array (
                        
                      => 
                        array (
                          
                      'string' => "Ein ASCII string",
                          
                      'detectEncoding' => "UTF-8",
                          
                      'debugClass' => "ASCII",
                        ),
                        
                      => 
                        array (
                          
                      'string' => "Text mit UTF8-Umlauten wie ä ",
                          
                      'detectEncoding' => "UTF-8",
                          
                      'debugClass' => "UTF-8",
                        ),
                        
                      => 
                        array (
                          
                      'string' => "Text mit UTF8-Umlauten wie \xe4 ",
                          
                      'detectEncoding' => "ISO-8859-1",
                          
                      'debugClass' => "ISO-8859-1",
                        ),
                        
                      => 
                        array (
                          
                      'string' => "Text mit \xe4 und \x80 ",
                          
                      'detectEncoding' => "ISO-8859-1",
                          
                      'debugClass' => "CP1252",
                        ),

                      ASCII ist eine echte Untermenge von UTF-8. So gesehen nicht ganz falsch, sollte aber ASCII liefern, weil in der Liste der möglichen Ergebnisse enthalten ist.
                      Fraglich auch der letzte Test. "\x80" ein typisches CP1252-Zeichen (€) und unter ISO/IEC 8859-1 sehr selten und nur zulässig mit der Ergänzung ISO/IEC 6429.
                      Zudem wirft die Funktion mehrere Notice: iconv(): Detected an illegal character in input string ..

                      Der einfache Test zeigt schon, es bleibt beim Raten. Eine sichere Bestimmung ist nicht möglich. Hab es bei mir drin gelassen, da es schon öfter beim Import vom Fremddaten hilfreich war.

                      LG jspit


                      Kommentar

                      Lädt...
                      X