В этом посте речь пойдет о работе РНР5 с multibyte строками посредством preg_*() функций.
Заметил интересное положение дел, вобщем-то давным давно описанное в интернете, но актуальное и по сей день (вопрос всплыл всвязи с недавним постом про trim()).
Для примера приведу небольшой скрипт:
Рабочий пример лежит по адресу http://test.dis.dj/utf/.
Какие выводы следует сделать из увиденного:
Полезные ссылки:
Ну чтож, ждем PHP6, где обещается нормальная поддержка строк в UTF, включая BOM, который завалит наш сценарий, выведя 3 байта перед header(). Собственно в РНР6 вообще будет много бонусов…
P.S. Пост ни в коем случае не претендует на «открытие Америки» — я лишь собрал известную мне инфу.
UPD. В процессе обсуждения пришли к следующей замене «\w»: либо рекомендованный конгломерат «(?:\p{L}|\p{M}|\p{D}|\p{Pc})», либо «[\p{L}\p{Nd}]» (если хочется покороче). Спасибо khim.
Заметил интересное положение дел, вобщем-то давным давно описанное в интернете, но актуальное и по сей день (вопрос всплыл всвязи с недавним постом про trim()).
Для примера приведу небольшой скрипт:
<?<br> <br> print "Локаль: " . setLocale(LC_ALL, 0) . "\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 ($usePatch) mb_preg_match_all($pattern, $test, $matches, PREG_OFFSET_CAPTURE);<br> else preg_match_all($pattern, $test, $matches, PREG_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_subject, 0, $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_subject, 0, $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/.
Какие выводы следует сделать из увиденного:
- Смещение относительно начала строки считается всегда в байтах:
3 байта «one» +
1 байт пробел +
3×2 байта «два» +
1 байт пробел +
3×2 байта «два» +
1 байт пробел =
18 байт,
а должно быть
3 + 1 + 3 + 1 + 3 + 1 = 12 символов. - Правильно распознает кириллицу только «Character range» с ключем «/u» и модификатор «\pL», означающий «Unicode letter»
- Модификатор «\w» с кириллицей не работает вообще, даже ключ «/u» не помогает
- На сервере под управлением Windows Server 2008 по неизвестной мне причине отработала самая первая конструкция, а с ключем «/u» уже нет :)
Полезные ссылки:
- Ветка форума Codenet.
- Подробнее про движок PCRE и про модификаторы можно прочитать в официальной документации.
- В другой ветке на ixbt было неплохо написано про «/u».
- В комментариях к preg_match_all есть функция mb_preg_match_all, которая конвертирует отступы в правильные (она как раз и используется в данном посте).
Ну чтож, ждем PHP6, где обещается нормальная поддержка строк в UTF, включая BOM, который завалит наш сценарий, выведя 3 байта перед header(). Собственно в РНР6 вообще будет много бонусов…
P.S. Пост ни в коем случае не претендует на «открытие Америки» — я лишь собрал известную мне инфу.
UPD. В процессе обсуждения пришли к следующей замене «\w»: либо рекомендованный конгломерат «(?:\p{L}|\p{M}|\p{D}|\p{Pc})», либо «[\p{L}\p{Nd}]» (если хочется покороче). Спасибо khim.