Обеспечение действительного 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;
}
}
Ответы
Ответ 1
UTF-8 может хранить любой символ Юникода. Если ваша кодировка - это что-то еще, в том числе 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 документ
Вы, конечно же, хотите установить для этого параметра "strict" значение TRUE. Но я не уверен, что это действительно охватывает все недопустимые последовательности UTF-8. Функция не претендует на проверку байтовой последовательности для действительности UTF-8. Известны случаи, когда mb_detect_encoding раньше догадывался UTF-8, хотя я не знаю, может ли это произойти в строгом режиме.
Если вы хотите быть уверенным, сделайте это самостоятельно, используя рекомендованное W3 regex:
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);
Ответ 2
С библиотекой mbstring у вас есть mb_check_encoding().
Пример использования:
mb_check_encoding($string, 'UTF-8');
В 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 с
Ответ 3
Просто примечание: вместо использования часто рекомендуемого (довольно сложного) регулярного выражения W3C вы можете просто использовать модификатор 'u' для проверки строки для достоверности UTF-8:
<?php
if (preg_match("//u", $string)) {
// $string is valid UTF-8
}
Ответ 4
Посмотрите на http://www.phpwact.org/php/i18n/charsets для руководства о наборах символов. Эта страница ссылается на страницу специально для UTF-8.
Ответ 5
Ответ на "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 (плохо сформированный UTF-8).
Ответ 6
Я не уверен, что это даст то же самое, но не могли бы вы просто использовать utf8_encode()
для всего текста, не беспокоясь об обнаружении?
Если текст уже UTF-8, это не повредит. И если это не так, он будет преобразован. Если вы уже думали об этом, есть ли причина, по которой это не сработает?