PHP, PREG und UTF-8

    In diesem Beitrag werden wir über PHP5 sprechen, das mit Multibyte-Strings arbeitet, die preg _ * () -Funktionen verwenden.

    Mir ist ein interessanter Sachverhalt aufgefallen, der im Allgemeinen schon lange im Internet beschrieben wurde, aber bis heute relevant ist (die Frage ist im Zusammenhang mit einem kürzlich veröffentlichten Beitrag über trim () aufgetaucht ).

    Zum Beispiel gebe ich ein kleines Skript:

        
        print "Локаль: " . setLocale(LC_ALL, 0) . "\n";
        
        /**
         * Выводит результаты функции preg_match_all
         * @param string $comment  Комментарий
         * @param string $pattern  Паттерн для preg_match_all
         * @param bool   $usePatch Использовать ли патч
         * @return void
         */
        
        function preg_test($comment, $pattern, $usePatch = false) {
            
            $test = "one два два three";
            
            print "\n{$comment}: {$pattern}\n";
            
            if ($usePatch) mb_preg_match_all($pattern, $test, $matches, PREG_OFFSET_CAPTURE);
            else preg_match_all($pattern, $test, $matches, PREG_OFFSET_CAPTURE);
            
            foreach ($matches[0] as $v) print "  Подстрока: «{$v[0]}», смещение: {$v[1]}\n";
            
        }
        
        /**
         * Патч для устранения проблемы с оффсетами, осуществляет только их пересчет
         */
        
        function mb_preg_match_all(
            $ps_pattern,
            $ps_subject,
            &$pa_matches,
            $pn_flags = PREG_PATTERN_ORDER,
            $pn_offset = 0,
            $ps_encoding = NULL
        ) {
            
            // WARNING! - All this function does is to correct offsets, nothing else:
            //(code is independent of PREG_PATTER_ORDER / PREG_SET_ORDER)
            
            if (is_null($ps_encoding)) $ps_encoding = mb_internal_encoding();
            
            $pn_offset = strlen(mb_substr($ps_subject, 0, $pn_offset, $ps_encoding));
            $ret = preg_match_all($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset);
            
            if ($ret && ($pn_flags & PREG_OFFSET_CAPTURE))
                foreach($pa_matches as &$ha_match)
                    foreach($ha_match as &$ha_match)
                        $ha_match[1] = mb_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding);
            
            return $ret;
            
        }
        
        preg_test("«В лоб»", "/[\w]+/i");
        preg_test("Character range", "/[а-яa-z]+/i");
        preg_test("«В лоб» с ключем «/u»", "/[\w]+/ui");
        preg_test("Character range с ключем «/u»", "/[а-яa-z]+/ui");
        preg_test("Модификатор «\pL», можно даже без «/u»", "/[\pL]+/i");
        preg_test("Модификатор «\p{Cyrillic}», можно тоже без «/u»", "/[\p{Cyrillic}]+/i");
        preg_test("(!) Модификатор «\pL» с патчем", "/[\pL]+/i", true);
        
        $source = highlight_file(__FILE__, true);
        
    ?>


    Ein funktionierendes Beispiel finden Sie unter http://test.dis.dj/utf/ .

    Welche Schlussfolgerungen sollten aus dem, was er sah, gezogen werden:
    1. Offset relativ zum Anfang der Zeile immer in Bytes:
      3 Byte «ein» +
      1 Byte Raumes +
      3 × 2 Byte "zwei" +
      1 - Byte - Raum +
      3 × 2 Byte "zwei" +
      1 - Byte - Raum =
      18 Bytes ,
      sondern muss sein
      3 + 1 + 3 + 1 + 3 + 1 = 12 Zeichen .
    2. Nur der "Zeichenbereich" mit der Taste "/ u" und dem Modifikator "\ pL", der "Unicode-Buchstabe" bedeutet, erkennt Kyrillisch korrekt
    3. Der Modifikator "\ w" mit dem kyrillischen Alphabet funktioniert überhaupt nicht, auch die Taste "/ u" hilft nicht
    4. Auf einem Server unter Windows Server 2008 funktionierte das allererste Design aus einem mir unbekannten Grund, aber mit der Option "/ u" nicht mehr :)

    Nützliche Links:

    Nun, wir warten auf PHP6, wo die normale Unterstützung von Strings in UTF, einschließlich der Stückliste, verspricht, die unser Skript durch die Ausgabe von 3 Bytes vor header () überfluten wird. Tatsächlich wird es in PHP6 im Allgemeinen viele Boni geben ...

    PS Der Post behauptet in keiner Weise, Amerika zu entdecken - ich habe nur die Informationen gesammelt, die ich kenne.

    UPD. Während der Diskussion kamen wir zu folgendem Ersatz \ w: entweder dem empfohlenen Konglomerat "(?: \ P {L} | \ p {M} | \ p {D} | \ p {Pc})" oder "[\ p {L} \ p {Nd}] ”(falls Sie eine kürzere haben möchten). Danke, khim .

    Jetzt auch beliebt: