Регулярное выражение Perl застревает в нескольких экземплярах наборов символов
Вопрос
Я начал с сумасшедших неудач при использовании preg_replace в php и свел это к проблемному случаю, когда несколько классов символов используют вместе турецкие буквы «i» с точкой и «ı» без точки.Вот простой тестовый пример на php:
<?php
echo 'match single normal i: ';
$str = 'mi';
echo (preg_match('!m[ıi]!', $str)) ? "ok\n" : "fail\n";
echo 'match single undotted ı: ';
$str = 'mı';
echo (preg_match('!m[ıi]!', $str)) ? "ok\n" : "fail\n";
echo 'match double normal i: ';
$str = 'misir';
echo (preg_match('!m[ıi]s[ıi]r!', $str)) ? "ok\n" : "fail\n";
echo 'match double undotted ı: ';
$str = 'mısır';
echo (preg_match('!m[ıi]s[ıi]r!', $str)) ? "ok\n" : "fail\n";
?>
И снова тот же тестовый пример в Perl:
#!/usr/bin/perl
$str = 'mi';
$str =~ m/m[ıi]/ && print "match single normal i\n";
$str = 'mı';
$str =~ m/m[ıi]/ && print "match single undotted ı\n";
$str = 'misir';
$str =~ m/m[ıi]s[ıi]r/ && print "match double normal i\n";
$str = 'mısır';
$str =~ m/m[ıi]s[ıi]r/ && print "match double undotted ı\n";
Первые три теста работают нормально.Последнее не совпадает.
Почему в качестве класса символов это работает нормально один раз, но не во второй раз в одном и том же выражении?Как мне написать выражение, соответствующее такому слову, которое должно совпадать независимо от того, какими комбинациями букв оно написано?
Редактировать: Справочная информация о языковая проблема Я пытаюсь программировать.
Редактировать 2: Добавление use utf8;
Директива исправляет версию Perl.Поскольку моя первоначальная проблема была связана с программой PHP, и я переключился на Perl только для того, чтобы проверить, не является ли это ошибкой в PHP, это мне не очень помогает. Кто-нибудь знает директиву, позволяющую PHP не подавиться этим?
Решение
Многобайтовые последовательности не будут делать то, что вы хотите, в классах символов в квадратных скобках, если UTF-8 неправильно интерпретируется как последовательность 8-битных байтов.Думаю об этом.Если [nñm]
неправильно интерпретируется не как три логических символа, а как четыре физических байта, вам будет соответствовать только символ, код которого равен 6E или C3, или B1, или 6D.
В некоторых целях вам может сойти с рук переписывание [nñm]
как (?:n|ñ|m)
.Это просто зависит от того, что вы делаете.Корпусные вещи не подойдут.
Кроме того, в Unicode есть специальные правила регистра для турецкого i без точки.
Похоже, PHP просто недостаточно современен.Вздох.
Другие советы
Возможно, вам придется сообщить Perl, что ваш исходный файл содержит символы utf8.Пытаться:
#!/usr/bin/perl
use utf8; # **** Add this line
$str = 'mısır';
$str =~ m/m[ıi]s[ıi]r/ && print "match double undotted ı\n";
Это не поможет вам с PHP, но в PHP может быть аналогичная директива.В противном случае попробуйте использовать какую-либо escape-последовательность, чтобы избежать помещения буквального символа в исходный код.Я ничего не знаю о PHP, поэтому не могу с этим помочь.
Редактировать
Я читаю, что PHP не поддерживает Unicode.Таким образом, входные данные Юникода, которые вы передаете, скорее всего, будут рассматриваться как строка байтов, в которую был закодирован Юникод.
Если вы можете быть уверены, что ваш ввод поступает в формате utf-8, вы можете сопоставить последовательность utf-8 для ı
который \xc4 \xb1
как в:
$str = 'mısır'; # Make sure this source-file is encoded as utf-8 or this match will fail
echo (preg_match('!m(i|\xc4\xb1)s(i|\xc4\xb1)r!', $str)) ? "ok\n" : "fail\n";
Это работает?
Отредактируйте еще раз:
Я могу объяснить, почему ваши первые три теста прошли успешно.Давайте представим, что в вашей кодировке ı
кодируется как ABCDE
.тогда PHP видит следующее:
echo 'match single normal i: ';
$str = 'mi';
echo (preg_match('!m[ABCDEi]!', $str)) ? "ok\n" : "fail\n";
echo 'match single undotted ABCDE: ';
$str = 'mABCDE';
echo (preg_match('!m[ABCDEi]!', $str)) ? "ok\n" : "fail\n";
echo 'match double normal i: ';
$str = 'misir';
echo (preg_match('!m[ABCDEi]s[ABCDEi]r!', $str)) ? "ok\n" : "fail\n";
echo 'match double undotted ABCDE: ';
$str = 'mABCDEsABCDEr';
echo (preg_match('!m[ABCDEi]s[ABCDEi]r!', $str)) ? "ok\n" : "fail\n";
что делает очевидным, почему первые три теста пройдены, а последний — нет.Если вы используете начальную/конечную привязку ^...$
Я думаю, вы обнаружите, что пройден только первый тест.