Обеспечение действительного 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, это не повредит. И если это не так, он будет преобразован. Если вы уже думали об этом, есть ли причина, по которой это не сработает?