Обеспечение действительного utf-8 в PHP
Я использую PHP для обработки текста из различных источников. Я не ожидаю, что это будет что-то кроме UTF-8, ISO-8859-1 или, возможно, WINDOWS-1252. Если это что-то отличное от одного из них, мне просто нужно убедиться, что текст превращается в правильную строку UTF-8, даже если символы потеряны. Решает ли это опция //TRANSLIT для iconv? Например, будет ли этот код гарантировать, что строка безопасна для вставки в документ (или базу данных) в кодировке UTF-8?
function make_safe_for_utf8_use($string) {
$encoding = mb_detect_encoding($string, "UTF-8,ISO-8859-1,WINDOWS-1252");
if ($encoding != 'UTF-8') {
return iconv($encoding, 'UTF-8//TRANSLIT', $string);
} else {
return $string;
}
}
6 ответов
UTF-8 может хранить любой символ Unicode. Если ваша кодировка - это что-то еще, включая ISO-8859-1 или Windows-1252, UTF-8 может хранить в ней каждый символ. Таким образом, вам не нужно беспокоиться о потере каких-либо символов при преобразовании строки из любой другой кодировки в UTF-8.
Кроме того, ISO-8859-1 и Windows-1252 являются однобайтовыми кодировками, в которых допустим любой байт. Технически невозможно различить их. Я бы выбрал Windows-1252 в качестве соответствия по умолчанию для последовательностей, отличных от UTF-8, поскольку единственные байты, которые по-разному декодируют, это диапазон 0x80-0x9F. Они декодируют различные символы, такие как умные кавычки и евро в Windows-1252, тогда как в ISO-8859-1 они являются невидимыми управляющими символами, которые почти никогда не используются. Веб-браузеры могут иногда говорить, что они используют ISO-8859-1, но часто они действительно будут использовать Windows-1252.
будет ли этот код гарантировать, что строка безопасна для вставки в документ в кодировке UTF-8
Вы, конечно, захотите установить для этого необязательного "строгого" параметра значение TRUE. Но я не уверен, что это действительно охватывает все недопустимые последовательности UTF-8. Функция не претендует на явную проверку последовательности байтов на достоверность UTF-8. Известны случаи, когда mb_detect_encoding раньше угадывал UTF-8, хотя я не знаю, может ли это все-таки произойти в строгом режиме.
Если вы хотите быть уверены, сделайте это самостоятельно, используя регулярное выражение, рекомендованное W3:
if (preg_match('%^(?:
[\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
)*$%xs', $string))
return $string;
else
return iconv('CP1252', 'UTF-8', $string);
С библиотекой mbstring у вас есть http://fr.php.net/mb_check_encoding.
Пример использования:
mb_check_encoding($string, 'UTF-8');
Когда производительность имеет значение, это быстрее, чем регулярное выражение, представленное в принятом ответе.
Быстрый тест на моей конфигурации показывает (для 20 000 итераций):
- регулярное выражение:~ 310 мс
- mb_check_encoding:~ 90мс
РЕДАКТИРОВАТЬ
Благодаря PHP 7.1.9 в последней системе Windows 10 решение regex превосходит mb_check_encoding()
для любой длины строки (до 20 000 итераций):
- 10 символов: регулярное выражение => 4 мс,
mb_check_encoding()
=> 64 мс - 10000 символов: регулярное выражение => 125 мс,
mb_check_encoding()
=> 2,4 с
Просто примечание: вместо того, чтобы использовать часто рекомендуемое (довольно сложное) регулярное выражение W3C, вы можете просто использовать модификатор 'u', чтобы проверить строку на валидность UTF-8:
<?php
if (preg_match("//u", $string)) {
// $string is valid UTF-8
}
Ответ на "iconv идемпотент"
не iconv - iconv не идемпотентный
большая разница между utf8_encode() и iconv() заключается в том, что iconv может вызывать такие ошибки, как "Обнаружен неполный многобайтовый символ во входной строке" даже при
iconv('ISO-8859-1', 'UTF-8'.'//IGNORE', $str)
в приведенном выше коде:
$ encoding = mb_detect_encoding ($ string, "UTF-8, ISO-8859-1, WINDOWS-1252");
Вы должны знать, что mb_detect_encoding может ответить на uft-8 даже за недопустимые строки utf-8 (плохо сформированный utf8)
Взгляните на http://www.phpwact.org/php/i18n/charsets для руководства по кодировкам. Эта страница ссылается на страницу специально для utf8.
Не уверен, что это даст то же самое, но вы не могли бы просто использовать utf8_encode()
на весь текст, не беспокоясь об обнаружении? Если текст уже UTF-8, это не повредит. И если это не так, он будет преобразован. Если вы уже думали об этом, есть ли причина, по которой это не сработает?