webew
Войти » Регистрация
 
PHP

Глюк регулярных выражений в PHP при работе с UTF-8

6 декабря 2008, 7:58
Автор: 1234ru
Нашел такую вещь: если указывать модификатор 'u' (PCRE_UTF8), который предписывает интерпретировать шаблон как строку в utf-8 (см. также http://ru2.php.net/manual/ru/reference.pcre.pattern.modifiers.php), для русских букв работает регистронезависимое сопоставление, но в то же время русские буквы не считаются буквами!

<?php
# скрипт в UTF-8
echo preg_match('/г/ui', 'Г'); // выведет 1 - понимает, что 'г' и 'Г' - одна и та же буква
echo preg_match('/\w/u', 'Г'); // выведет 0 - не понимает, что 'Г' - буква!
?>


Особенно странное заключается в том, что без модификатора буква распознаётся (хотя регистронезависимое сопоставление, конечно же, не работает, что, впрочем, не странно):

<?php
echo preg_match('/г/i', 'Г'); // выведет 0, что неудивительно, т.к. не используется модификатор
echo preg_match('/\w/', 'Г'); // выведет 1
?>



Неожиданным оказалось, что, если сохранить скрипт, например, в кодировке cp-1251, то сопоставление букв разного регистра может проходить успешно:

<?php
# скрипт в CP-1251
echo preg_match('/г/i', 'Г'); // выведет 1
echo preg_match('/\w/', 'Г'); // также выведет 1
?>


Проверялось всё это на PHP 5.2.4 под Windows.

Немало удивил также результат проверки на PHP 5.1.6 под Linux: отсутсвие совпадения для всех шести описанных случаев, т.е. модификатор PCRE_UTF8 не работает вообще.


Что бы с этим сделать, как думаете?
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
NO USERPIC

rgbeast

А работает ли mb_ereg_replace? Функция конечно требует расширение mb.
06.12.2008, 11:06
Ответить

1234ru

Не работает:

<?php

echo mb_regex_encoding('utf-8'); // выведет 1 - установление кодировки прошло удачно
echo mb_internal_encoding('utf-8'); // также выведет 1 (на всякий случай, чтобы внять все вопросы)
echo mb_ereg_match('/г/ui', 'Г'); // выводит пустую строку, => совпадения не было
echo mb_ereg_match('/\w/u', 'Г'); // для этого примера и следующих также выводится пустая строка
echo mb_ereg_match('/г/i', 'Г');
echo mb_ereg_match('/\w/', 'Г');

?>

Такая же ситуация с mb_ereg(). Проверялось под Linux (PHP 5.1.6).

Кстати, я что-то не пойму, зачем было делать две разных функции: mb_ereg() и mb_ereg_match().
То, что не убивает нас, делает нас инвалидами.
07.12.2008, 00:46
Ответить
NO USERPIC

rgbeast

Синтаксис у функцмм другой, модификаторов нет:
echo mb_ereg_match('г', 'Г');

Опции - третий аргумент
07.12.2008, 01:20
Ответить

1234ru

Да, работает вот так: mb_ereg_match('г', 'Г', 'i'); (без модификатора 'i', кстати, не работает).

Работает и mb_eregi('г', 'Г');.

Но все равно мне все это не нравится. ereg_match() - отстой, массив вхождений не возвращает. eregi() - тоже - не принимает модификаторы.

Зачем вообще эти ereg()? Ведь надо ж было переделать под mb_ четыре функции: preg_match(), preg_match_all, preg_replace() и preg_replace_callback(). Пока этого нет, будет всё через одно место.
То, что не убивает нас, делает нас инвалидами.
07.12.2008, 01:45
Ответить
NO USERPIC

kugu

Цитата:
Зачем вообще эти ereg()?


Представь себе это основные функции для работы с регулярками в ПХП.

А preg_& это Perl style, портированный в PHP по просьбам трудящихся.

По поводу того как использовать *ereg* функции, передавать модификаторы и получать вхождения достаточно развернуто сказано на php.net. А по поводу в чем сильные и слабые стороны обоих стандартов холи варов миллион было уже. В т.ч. и сравнительные тесты производительности найти не проблема.

Есть подозрения, что mb_preg* никогда не будет, именно потому что этот стиль подразумевает модификаторы, в т.ч. "u", А в ereg* модификаторов вообще небыло, вместо них плодят разные функции типа eregi и mb_ereg.

И тут мы плавно подошли к главному, в ereg нет и не надо (!) модификаторов.

echo mb_ereg('Г', 'Г'); // так матчим
echo mb_eregi('г', 'Г'); // так матчим регистронезависимо

в обоих случаях 3 параметр - массив вхождений.

RTFM
09.12.2008, 16:46
Ответить

1234ru

Цитата:
... как использовать *ereg* функции, передавать модификаторы ...

Что-то не нашел я в описании функции ereg() возможности передавать модификаторы.

Цитата:
И тут мы плавно подошли к главному, в ereg нет и не надо (!) модификаторов.

Что будем делать, когда понадобятся 's', 'm' и др.?

Нередко возникает задача, которую можно решить с помощью PCRE, а с помощью POSIX - нет. Думаю, использовать последние можно лишь не от хорошей жизни, когда разница в производительности даже в 2-3 раза важна, а в остальных случаях смысла я в этом не вижу.
То, что не убивает нас, делает нас инвалидами.
12.12.2008, 02:45
Ответить

Serg_pnz

1234ru, чем закончились изыскания? Сегодня пол-дня убил, ну не понимает preg_replace когда вводишь fefысывсы

upd: это тоже не прокатило

$regLoginTmp = mb_eregi_replace ('[^a-zA-Z0-9а-яА-Я]', '', $regLogin);
16.03.2009, 15:31
Ответить

Serg_pnz

Не фонтан, но вроде работает для моей узкой задачи.

        $cp12_pat = iconv("UTF-8", "CP1251", "a-zA-Zа-яА-Я0-9-_ ");
        $regLoginTmp = iconv("UTF-8", "CP1251", $regLogin);
        $regLoginTmp = preg_replace('/[^'.$cp12_pat.']/si', '', $regLoginTmp);     
        $regLoginTmp = iconv("CP1251", "UTF-8", $regLoginTmp);
       
        if ( $regLogin!=$regLoginTmp ) {
            echo " Служебные символы недопустимы";
            echo "<script>$('regLogin').value='".$regLoginTmp."'</script>";
            $stop += 1;
        }


2admin: сделайте "шапку" для оформления кода php по примеру цитаты
16.03.2009, 15:57
Ответить

1234ru

Цитата:
2admin: сделайте "шапку" для оформления кода php по примеру цитаты

Спасибо, рассмотрим такой вариант.
То, что не убивает нас, делает нас инвалидами.
20.03.2009, 19:12
Ответить

1234ru

Изыскания закончились прочтением книги Дж. Фридла про регулярные выражения, в которой написано, что диалект PCRE интерпретирует символ \w точно как ASCII-[A-Za-z0-9] - не включая буквы остальных кодировок, хотя последовательности вида A-Я понимает.

Вам поможет модификатор 'u' - preg_replace('/[A-Za-z0-9А-Яа-я_]+/u', '', 'fefысывсы') вернет пустую строку.

P.S. Извините, что поздно ответил. Диссертация одолевает.
То, что не убивает нас, делает нас инвалидами.
20.03.2009, 19:11
Ответить

Serg_pnz

Дисс - нужная вещь, я вот в своё время не стал поступать(((

Цитата:
Вам поможет модификатор 'u' - preg_replace('/[A-Za-z0-9А-Яа-я_]+/u', '', 'fefысывсы') вернет пустую строку.

Пробовал с u, но почему-то не прокатило. Может еще где-то ошибся...
Вопрос: зачем плюсик нужен, может из-за него не работало?
Цитата:
[A-Za-z0-9А-Яа-я_]+


u должна быть u или U?
20.03.2009, 19:58
Ответить

1234ru

Цитата:
Пробовал с u, но почему-то не прокатило. Может еще где-то ошибся...

Странно.. Что, вот прямо приводимый мной пример не работает?
Какая версия PHP?

Цитата:
Вопрос: зачем плюсик нужен, может из-за него не работало?
Нет, дело не в плюсике. Плюсик на самом деле не нужен (это я нечаянно его поставил, а потом стереть забыл), хотя работать с ним и без него будет почти одинаково.

u должна быть именно маленькая (U большое - это совсем другое - см. http://ru.php.net/manual/ru/reference.pcre.pattern.modifiers.php)

Да, кстати. У Вас точно файл в UTF-8? Проверьте на всякий случай.
То, что не убивает нас, делает нас инвалидами.
20.03.2009, 20:58
Ответить

Serg_pnz

В который раз выручили! Всё прекрасно работает и без изврата с перекодировкой.
21.03.2009, 15:01
Ответить

Serg_pnz

Еще одна заморочка для utf-8

Пользователь вводит данные на русском языке и проверка

if ( strlen($regLoginTmp) < 4 ) $stop[] =1;


уже не работает.

Пришлось извратиться так

$sum1 = preg_match_all("#(.){1,1}#sui", $regLoginTmp, $tmp1);
if ( $sum1 < 4 ) $stop[] =1;
10.04.2009, 10:32
Ответить

1234ru

Если $regLoginTmp содержит нелатинские буквы, и данные в UTF-8, то нужно использовать не strlen, а mb_strlen (в стандартном пакете PHP её нет, нужно устанавливать доп. модуль)

Как бы то ни было, с рег. выражением Вы перемудрили: #(.){1,1}# - то же, что просто #.# :)
(кстати; вместо {1,1} можно писать просто {1})
То, что не убивает нас, делает нас инвалидами.
10.04.2009, 10:53
Ответить

Serg_pnz

Спасибо, помогли нубу))
10.04.2009, 11:49
Ответить
NO USERPIC

dskarataev

Так что же все-таки является самым правильным решением?

1.каждый раз при использовании регулярных выражений делать конвертацию через iconv()
2.переучиваться на синтаксис PHP (ereg)
3.100% решением является использование [a-zA-Zа-яА-Я0-9] и модификатора u

хотелось бы конечно третье :)
07.10.2010, 17:27
Ответить

1234ru

Цитата:
хотелось бы конечно третье :)


Так и есть.
[a-zA-Z\dА-Яа-яЁё] - дедовский способ, который подходит практически для любой кодировки, если там соотв. символы следуют подряд.
100%-ность определяется тем, подряд ли следуют соотв. буквы в таблице символов.

Но есть более правильное решение: конкретно для UTF-8 PHP и Perl поддерживают так называемые свойства Юникода - аналог предопределенных классов (типа \w, \d и др.)

Классы эти имеют вид \p{что-то}. Например, \p{L} - это буквы (есть также полная запись - \p{Letters}), \p{Lu} - прописные буквы, \p{Cyrillic} - кириллические символы и др.
В общем, классов этих куча. Попозже как-нибудь ссылку найду (не забыть бы).

Поэтому современный цивилизованный способ - это выражение вида $pattern = '/\p{L}/u';.
То, что не убивает нас, делает нас инвалидами.
08.10.2010, 01:24
Ответить
Добавить комментарий
Отображение комментариев: Древовидное | Плоское
© 2008—2024 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100

Реклама: