Регулярное выражение для обнаружения неверной строки UTF-8
В PHP мы можем использовать mb_check_encoding()
определить, является ли строка допустимой UTF-8. Но это не переносимое решение, так как оно требует, чтобы расширение mbstring было скомпилировано и включено. Кроме того, он не скажет нам, какой символ является недействительным.
Существует ли регулярное выражение (или другой 100% переносимый метод), которое может соответствовать недопустимым байтам UTF-8 в данной строке. Таким образом, эти байты можно заменить при необходимости (сохраняя двоичную информацию, например, при создании тестового выходного XML-файла, который включает двоичные данные). Поэтому преобразование символов в UTF-8 приведет к потере информации. Итак, мы можем захотеть преобразовать:
"foo" . chr(128) . chr(255)
В
"foo<128><255>"
Поэтому, просто "обнаружив", что строка недостаточно хороша, нам нужно было бы определить, какие символы недопустимы.
3 ответа
Вы можете использовать это регулярное выражение PCRE для проверки правильности UTF8 в строке. Если регулярное выражение совпадает, строка содержит недопустимые последовательности байтов. Он на 100% портативен, потому что не требует компиляции в PCRE_UTF8.
$regex = '/(
[\xC0-\xC1] # Invalid UTF-8 Bytes
| [\xF5-\xFF] # Invalid UTF-8 Bytes
| \xE0[\x80-\x9F] # Overlong encoding of prior code point
| \xF0[\x80-\x8F] # Overlong encoding of prior code point
| [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
| [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
| [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
| (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
| (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
| (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
| (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
| (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
)/x';
Мы можем проверить это, создав несколько вариантов текста.
// Overlong encoding of code point 0
$text = chr(0xC0) . chr(0x80);
var_dump(preg_match($regex, $text)); // int(1)
// Overlong encoding of 5 byte encoding
$text = chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80);
var_dump(preg_match($regex, $text)); // int(1)
// Overlong encoding of 6 byte encoding
$text = chr(0xFC) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80);
var_dump(preg_match($regex, $text)); // int(1)
// High code-point without trailing characters
$text = chr(0xD0) . chr(0x01);
var_dump(preg_match($regex, $text)); // int(1)
так далее...
Фактически, поскольку это соответствует недействительным байтам, вы можете использовать его в preg_replace, чтобы заменить их:
preg_replace($regex, '', $text); // Remove all invalid UTF-8 code-points
Я положил это здесь для полноты:
Предполагая, что PHP скомпилирован с PCRE, чаще всего он также включен с UTF-8. Таким образом, как явно указано в вопросе, это очень простое регулярное выражение может обнаружить недопустимые строки UTF-8, потому что они не будут совпадать:
preg_match('//u', $string);
Затем вы можете утверждать, что u
Модификатор (PCRE_UTF8) не всегда доступен, и правда, это может произойти, как показано в следующем вопросе:
Однако в моем практическом эфире это никогда не было проблемой. Более того, расширение PCRE вообще не доступно, что делает любой ответ, содержащий pcre, бесполезным (даже мой здесь). Но чаще всего эта проблема была больше проблемой прошлого на сегодняшний день минус несколько лет.
Более длинный ответ, подобный этому, был дан в несколько дублирующем вопросе:
Поэтому я думаю, что этот вопрос должен подчеркнуть больше преимуществ, которые предлагает предлагаемый ответ.
W3C имеет страницу (под названием Многоязычная кодировка формы), в которой перечислено следующее регулярное выражение Perl, которое соответствует допустимой строке UTF-8.
(Обратите внимание, что это противоположно регулярному выражению, указанному в другом ответе на этот вопрос SO, который соответствует недопустимой строке UTF-8.)
# Returns true if $field is UTF-8, and false otherwise.
$field =~
m/\A(
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)*\z/x;
Это работает для меня для обнаружения символов Юникода, смайликов ссылок или русских или китайских:
private function has_unicode( $string )
{
$pattern = '/^.*[^\x{00}-\x{00FF}]+.*$/u';
return preg_match( $pattern, $string ) ? true : false;
}