hawkmaster: Welche Zeichencodierung unter Windows ?

Hallo zusammen,

Folgendes ist mir nicht ganz klar.
Eine Datei wird unter Windows XP (Deutsch) mit dem Namen sÄÜ.txt einmal im UTF8 Format und einmal als ANSI gespeichert.

Wenn ich nun die länge der Datei mit PHP ermitteln möchte bekomme ich mit strlen den Wert 7 und mit mb_strlen($f, 'utf8') den Wert 6 unabhängig in welchem Format. Also immer gleich.

Ausgabe:
das file t ist s��.kdk mit länge 7 und 6

  
if(is_dir($useruploadtmp)){  
  $d = opendir($useruploadtmp);  
	while (false !== ($f = readdir($d))){  
	if(is_file($useruploadtmp . "/" . $f)){  
		$filename_ges = strlen($f);  
		$filename_ges_utf8 = mb_strlen($f, 'utf8');  
		echo "<br><br>das file t ist $f mit länge $filename_ges und $filename_ges_utf8<br>";  
	}  
}  
closedir($d);  
}  

Eigentlich rechnet strlen ja richtig obwohl ich annahm das die Zeichen ÄÜ doppelt gezählt werden.

Mache ich einen Denkfehler?
Welche Funktion ist die richtige?

vielen Dank und viele Grüße
hawk

  1. Hallo,

    Eine Datei wird unter Windows XP (Deutsch) mit dem Namen sÄÜ.txt einmal im UTF8 Format und einmal als ANSI gespeichert.

    deine weiteren Ausführungen lassen nur einen Schluss zu: Die obige Annahme ist falsch, die vermeintlich in UTF-8 gespeicherte Datei ist _nicht_ UTF-8.
    Vergleiche mal die Dateigröße: Die in ISO-8859-x oder Windows-1252 gespeicherte (also das, was Windows unter "ANSI" versteht) dürfte 7 Byte lang sein (oder 9, wenn sie am Ende des Strings noch einen Zeilenumbruch hat), die andere 9 (oder 11 mit zeilenumbruch), ggf. sogar 12 bzw. 14, wenn sie mit BOM gespeichert wurde. Hinweis: Notepad speichert UTF-8 immer mit BOM.

    Wenn ich nun die länge der Datei mit PHP ermitteln möchte bekomme ich mit strlen den Wert 7 und mit mb_strlen($f, 'utf8') den Wert 6 unabhängig in welchem Format. Also immer gleich.

    Ausgabe:
    das file t ist s??.kdk mit länge 7 und 6

    Das ist die in ISO ("ANSI") gespeicherte Fassung. Da darf mb_strlen() zu einem falschen Ergebnis kommen, weil Bytefolgen auftreten, die in UTF-8 nicht zulässig sind und die korrekte Zählung behindern.
    Und bei der in UTF-8 gespeicherten Version? Vermutlich 12 und 8?

    if(is_dir($useruploadtmp)){

    $d = opendir($useruploadtmp);
    while (false !== ($f = readdir($d))){
    if(is_file($useruploadtmp . "/" . $f)){
    $filename_ges = strlen($f);
    $filename_ges_utf8 = mb_strlen($f, 'utf8');
    echo "<br><br>das file t ist $f mit länge $filename_ges und $filename_ges_utf8<br>";
    }
    }
    closedir($d);
    }

      
    
    > Eigentlich rechnet strlen ja richtig obwohl ich annahm das die Zeichen ÄÜ doppelt gezählt werden.  
    > Mache ich einen Denkfehler?  
      
    Ja: Du verschweigst die Auswertung der in UTF-8 gespeicherten Datei.  
      
    So long,  
     Martin  
    
    -- 
    Die letzten Worte des Neandertalers:  
    Möchte doch zu gern wissen, was in der Höhle ist ...
    
    1. Hallo Martin,

      danke für deine Hilfe und Ausführung.

      Ja: Du verschweigst die Auswertung der in UTF-8 gespeicherten Datei.

      Ich hatte es eigentlich erwähnt und meinte mit:
      "Wenn ich nun die länge der Datei mit PHP ermitteln möchte bekomme ich mit strlen den Wert 7 und mit mb_strlen($f, 'utf8') den Wert 6 unabhängig in welchem Format. Also immer gleich."

      das die Werte sowohl für die ANSI als auch UTF8 gespeicherte Datei gleich sind.

      Mir geht es eigentlich nicht so sehr um den Inhalt der Datei sondern was wie Windows diese Datei mit Sonderzeichen bzw. Umlaute behandelt. Ist es IMMER ISO-8859-1 ?
      Ich möchte ja eigentlich nur die richtige Anzahl Zeichen des Dateinamens.
      Aber ich vermute mal, dass ich hier bei Windows dann weiterhin immer mit strlen() richtig liege, auch wenn der Dateinamen Umlaute enthält?

      PS: echo "der zeichensatz ist " . mb_internal_encoding();
      bringt bei mir die Ausgabe:
      der zeichensatz ist ISO-8859-1

      Auf was bezieht sich diese Funktion? ISt das wieder das Encoding von Windows?
      Weil meine komplette Anwendung ist auf UTF8 umgestellt.

      vielen Dank und viele Grüße
      hawk

      1. Hallo,

        danke für deine Hilfe und Ausführung.

        ... die wohl völlig an der Frage vorbeiging, weil ich dich einfach missverstanden hatte.

        Eine Datei wird unter Windows XP (Deutsch) mit dem Namen sÄÜ.txt einmal im UTF8 Format und einmal als ANSI gespeichert.

        Es geht dir also um den *Dateinamen*, nicht den Dateiinhalt. Das hatte ich gestern abend nicht verstanden. Auf die Idee, den Dateinamen zu betrachten, bin ich gar nicht gekommen, weil der völlig unabhängig davon ist, ob du eine Textdatei als "ANSI" oder "UTF-8" speicherst - das bezieht sich nämlich nur auf die Codierung des *Dateiinhalts*.

        Windows speichert Dateinamen intern immer in UCS-2, einer einfachen Variante von UTF-16. Es gibt allerdings verschiedene System-APIs, mit denen man auf das Dateisystem zugreifen kann. Es gibt Funktionen, die Namen als UCS-2 übergeben, und es gibt solche, die nur 1 Byte pro Zeichen verwenden. Windows verwendet dabei Windows-1252, d.h. die Codes 0x80..0x9F werden auch als gültige Zeichen verwendet.

        PHP benutzt unter Windows offensichtlich die 1-Byte-Funktionen. Windows muss also an der API-Schnittstelle von UCS-2 (2 Byte pro Zeichen, intern) nach Windows-1252 (1 Byte pro Zeichen) umcodieren. Dabei werden Zeichen, die in Windows-1252 nicht darstellbar sind, einfach in Fragezeichen '?' umgewandelt.[1]

        PHP reagiert wiederum unterschiedlich, je nachdem, mit welcher PHP-Funktion man den Verzeichnisinhalt abfragt. Während readdir() die Namen wie beschrieben (Fragezeichen für nicht darstellbare Zeichen) durchreicht, filtert glob() Namen, die solche Zeichen enthalten, einfach aus. Sie scheinen nicht zu existieren.

        Diesen Codeschnipsel habe ich übrigens zum Testen verwendet:

        <table>  
        <?php  
          
        // use either of the following pair of instructions:  
        //  * glob() with a foreach() loop  
        // or  
        //  * opendir() with a while() loop  
          
        //$names = glob("*.*");  
        //foreach ($names as $file)  
        $dir = opendir(".");  
        while (false!==($file=readdir($dir)))  
         { echo "<tr><td>$file</td><td>";            // file name as text  
           for ($i=0; $i<strlen($file); $i++)        //  
            { printf(" %02X", ord($file[$i]));       // file name as hex dump  
            }  
           echo "</td></tr>\n";                      // end of table row  
         }  
        ?>  
        </table>
        

        Er gibt mir die Liste aller Dateien im Verzeichnis einmal als Klartext aus, und daneben als Hexdump. So konnte ich leicht erkennen, welche Zeichencodes wirklich darin vorkommen.

        Mir geht es eigentlich nicht so sehr um den Inhalt der Datei sondern was wie Windows diese Datei mit Sonderzeichen bzw. Umlaute behandelt. Ist es IMMER ISO-8859-1 ?
        Ich möchte ja eigentlich nur die richtige Anzahl Zeichen des Dateinamens.

        Da war er, dein Hinweis, dass es dir um den Datei_namen_ geht. Aber den habe ich total überlesen, ich war auf die Codierung des Inhalts fixiert, weil der Name meist egal ist.

        Ich hoffe, mein zweiter Versuch hilft dir nun etwas weiter.

        So long,
         Martin

        [1] Es gibt Ausnahmen. So wird der Zier-Schrägstrich U+2215 zum Beispiel einfach in den herkömmlichen Slash 0x2F gewandelt, das Euro-Zeichen U+20AC in sein Windows-1252-Pendant 0x80.

        --
        Heutzutage gilt ein Mann schon dann als Gentleman, wenn er wenigstens die Zigarette aus dem Mund nimmt, bevor er eine Frau küsst.
          (Barbra Streisand, US-Schauspielerin)
        1. Hallo Martin,

          nochmals herzlichen Dank für deine tolle Erklärung.
          Jetzt weiss ich wenigstens mal wie das Windows intern handelt.
          Puh, das Thema Codierung und Zeichensätze ist unerschöpflich :-)

          So ganz im Detail habe ich es noch nicht verstanden, was da mit den Apis und dem umcodieren passiert.

          <<
          PHP benutzt unter Windows offensichtlich die 1-Byte-Funktionen. Windows muss also an der API-Schnittstelle von UCS-2 (2 Byte pro Zeichen, intern) nach Windows-1252 (1 Byte pro Zeichen) umcodieren. Dabei werden Zeichen, die in Windows-1252 nicht darstellbar sind, einfach in Fragezeichen '?' umgewandelt.[1]>>

          Verstehe ich es dann richtig, dass ich weiterhin hier mit strlen() arbeiten kann wenn es rein um die Länge eines Dateinamens unter Windows geht, da ja alle Zeichen 1 Byte pro zeichen sind?

          vielen Dank und viele Grüße
          hawk

          1. Hi,

            Puh, das Thema Codierung und Zeichensätze ist unerschöpflich :-)

            ja, allerdings!

            So ganz im Detail habe ich es noch nicht verstanden, was da mit den Apis und dem umcodieren passiert.

            Okay. Also nochmal etwas anders.

            ----------------------------------------------------
              |               File system (internal)             |
              |           UCS-2 (2 bytes per character)          |
              ------------^---------------------------^-----------
                          |                           |
                        UCS-2                       UCS-2
                          |                           |
                       ANSI-API                  Unicode-API
                 (compatibility layer)                |
                          |                           |
                     Windows-1252                   UCS-2
                     1 byte/char                 2 bytes/char
                          |                           |
                  --------V--------           --------V--------
                  | Application 1 |           | Application 2 |
                  -----------------           -----------------

            Ein Programmierer, der eine Anwendung für Windows schreibt, hat die Wahl, welches der beiden APIs er verwenden möchte. Entweder er nutzt das alte Kompatibilitäts-API, bei dem er sich um Codierungen nicht kümmern muss, weil es wie seit ASCII-Zeiten üblich 1 byte pro Zeichen verwendet, oder aber er verwendet das modernere Unicode-API (das es in Windows übrigens seit der Einführung der "langen Dateinamen" mit Windows 95 gibt), bei dem er aber wahrscheinlich alle Strings zwischen seiner Anwendung und dem Betriebssystem hin- und hercodieren muss, denn kaum eine Anwendung arbeitet intern mit UTF-16. Dafür kann er in Dateinamen Sonderzeichen aus dem Unicode-Zeichensatz verwenden. Aber lohnt sich der Aufwand, nur um in Dateinamen mehr Sonderzeichen nutzen zu können?

            Die PHP-Programmierer haben sich in der Windows-Version von PHP wohl für die erste Möglichkeit entschieden. Das heißt, sämtliche Dateinamen (Namen, nicht Inhalte!) sind als Windows-1252-codiert zu betrachten.

            Als wäre das noch nicht genug, gibt's natürlich auch noch den DOS Compatibility Layer. Denn im Konsolenfenster (DOS-Emulation) wird nochmal eine andere Zeichencodierung verwendet, nämlich CP437 - das ist der alte IBM-Zeichensatz, der auch fest in der Hardware aller Grafikkarten verankert ist und den alte DOS-Anwendungen erwarten. Aber damit müssen wir uns zum Glück nicht auch noch befassen.

            PHP benutzt unter Windows offensichtlich die 1-Byte-Funktionen. Windows muss also an der API-Schnittstelle von UCS-2 (2 Byte pro Zeichen, intern) nach Windows-1252 (1 Byte pro Zeichen) umcodieren. Dabei werden Zeichen, die in Windows-1252 nicht darstellbar sind, einfach in Fragezeichen '?' umgewandelt.[1]
            Verstehe ich es dann richtig, dass ich weiterhin hier mit strlen() arbeiten kann wenn es rein um die Länge eines Dateinamens unter Windows geht, da ja alle Zeichen 1 Byte pro zeichen sind?

            Ja. Ohne Einschränkung.

            Schönen Freitag noch,
             Martin

            --
            Soso, der Klügere gibt nach.
            Aber warum sollen sich immer nur die Dummen durchsetzen?  .oO(?)
            1. Hallo Martin,

              nochmals ganz vielen Dank für deine ausführlichen Informationen und Erläuterungen.
              Ich finde es wirklich klasse das sich jemand Zeit nimmt dies so verständlich zu erklären.

              vielen Dank und viele Grüße
              hawk