Pull to refresh

PHP, PREG и UTF-8

PHP *
В этом посте речь пойдет о работе РНР5 с multibyte строками посредством preg_*() функций.

Заметил интересное положение дел, вобщем-то давным давно описанное в интернете, но актуальное и по сей день (вопрос всплыл всвязи с недавним постом про trim()).

Для примера приведу небольшой скрипт:

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

Рабочий пример лежит по адресу http://test.dis.dj/utf/.

Какие выводы следует сделать из увиденного:
  1. Смещение относительно начала строки считается всегда в байтах:
    3 байта «one» +
    1 байт пробел +
    3×2 байта «два» +
    1 байт пробел +
    3×2 байта «два» +
    1 байт пробел =
    18 байт,
    а должно быть
    3 + 1 + 3 + 1 + 3 + 1 = 12 символов.
  2. Правильно распознает кириллицу только «Character range» с ключем «/u» и модификатор «\pL», означающий «Unicode letter»
  3. Модификатор «\w» с кириллицей не работает вообще, даже ключ «/u» не помогает
  4. На сервере под управлением Windows Server 2008 по неизвестной мне причине отработала самая первая конструкция, а с ключем «/u» уже нет :)

Полезные ссылки:

Ну чтож, ждем PHP6, где обещается нормальная поддержка строк в UTF, включая BOM, который завалит наш сценарий, выведя 3 байта перед header(). Собственно в РНР6 вообще будет много бонусов…

P.S. Пост ни в коем случае не претендует на «открытие Америки» — я лишь собрал известную мне инфу.

UPD. В процессе обсуждения пришли к следующей замене «\w»: либо рекомендованный конгломерат «(?:\p{L}|\p{M}|\p{D}|\p{Pc})», либо «[\p{L}\p{Nd}]» (если хочется покороче). Спасибо khim.
Tags: phputf-8preg
Hubs: PHP
Total votes 43: ↑39 and ↓4 +35
Comments 17
Comments Comments 17

Popular right now