Определите кодировку и сделайте все в UTF-8 - PullRequest
287 голосов
/ 26 мая 2009

Я читаю много текстов из различных RSS-каналов и вставляю их в свою базу данных.

Конечно, в каналах используется несколько различных кодировок символов, например, UTF-8 и ISO-8859-1.

К сожалению, иногда возникают проблемы с кодировкой текстов. Пример:

  1. «ß» в «Fußball» в моей базе данных должно выглядеть так: «ÂŸ». Если это «В», оно отображается правильно.

  2. Иногда «ß» в «Fußball» в моей базе данных выглядит так: «ÃƒÂŸ». Тогда это отображается неправильно, конечно.

  3. В других случаях «ß» сохраняется как «ß» - то есть без каких-либо изменений. Тогда это также отображается неправильно.

Что я могу сделать, чтобы избежать случаев 2 и 3?

Как я могу сделать все в той же кодировке, желательно UTF-8? Когда я должен использовать utf8_encode(), когда я должен использовать utf8_decode() (ясно, каков эффект, но когда я должен использовать функции?) И когда я ничего не должен делать с вводом?

Можете ли вы помочь мне и скажите, как сделать так, чтобы кодировка была одинаковой? Возможно с функцией mb_detect_encoding()? Могу ли я написать функцию для этого? Итак, мои проблемы:

  1. Как узнать, какую кодировку использует текст?
  2. Как преобразовать его в UTF-8 - какой бы ни была старая кодировка?

Будет ли такая функция работать?

function correct_encoding($text) {
    $current_encoding = mb_detect_encoding($text, 'auto');
    $text = iconv($current_encoding, 'UTF-8', $text);
    return $text;
}

Я проверял это, но это не работает. Что с ним не так?

Ответы [ 24 ]

342 голосов
/ 13 августа 2010

Если вы примените utf8_encode() к уже строке UTF8, она вернет искаженный вывод UTF8.

Я сделал функцию, которая решает все эти проблемы. Называется Encoding::toUTF8().

Вам не нужно знать, какова кодировка ваших строк. Это может быть Latin1 (iso 8859-1), Windows-1252 или UTF8, или в строке может быть их сочетание. Encoding::toUTF8() преобразует все в UTF8.

Я сделал это, потому что служба давала мне все данные в беспорядке, смешивая UTF8 и Latin1 в одной строке.

Использование:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::toUTF8($utf8_or_latin1_or_mixed_string);

$latin1_string = Encoding::toLatin1($utf8_or_latin1_or_mixed_string);

Скачать:

https://github.com/neitanod/forceutf8

Обновление:

Я включил еще одну функцию, Encoding::fixUFT8(), которая будет исправлять каждую строку UTF8, которая выглядит искаженной.

Использование:

require_once('Encoding.php'); 
use \ForceUTF8\Encoding;  // It's namespaced now.

$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

Примеры:

echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");

выведет:

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football

Обновление: я преобразовал функцию (forceUTF8) в семейство статических функций класса Encoding. Новая функция Encoding::toUTF8().

72 голосов
/ 26 мая 2009

Сначала вы должны определить, какая кодировка была использована. Когда вы анализируете RSS-каналы (возможно, через HTTP), вы должны прочитать кодировку из параметра charset поля заголовка HTTP Content-Type . Если его нет, прочитайте кодировку из атрибута encoding инструкции обработки XML *1007*. Если этого тоже не хватает, используйте UTF-8, как определено в спецификации .


Редактировать Вот что я, вероятно, хотел бы сделать:

Я бы использовал cURL для отправки и получения ответа. Это позволяет вам устанавливать определенные поля заголовка и извлекать заголовок ответа. После получения ответа вы должны проанализировать HTTP-ответ и разделить его на заголовок и тело. Затем заголовок должен содержать поле заголовка Content-Type, которое содержит тип MIME и (мы надеемся) параметр charset с кодировкой / charset. Если нет, мы проанализируем XML PI на наличие атрибута encoding и получим оттуда кодировку. Если этого также не хватает, спецификации XML определяют использование UTF-8 в качестве кодировки.

$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';

$accept = array(
    'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
    'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
    'Accept: '.implode(', ', $accept['type']),
    'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
    // error fetching the response
} else {
    $offset = strpos($response, "\r\n\r\n");
    $header = substr($response, 0, $offset);
    if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
        // error parsing the response
    } else {
        if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
            // type not accepted
        }
        $encoding = trim($match[2], '"\'');
    }
    if (!$encoding) {
        $body = substr($response, $offset + 4);
        if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
            $encoding = trim($match[1], '"\'');
        }
    }
    if (!$encoding) {
        $encoding = 'utf-8';
    } else {
        if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
            // encoding not accepted
        }
        if ($encoding != 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $encoding);
        }
    }
    $simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
    if (!$simpleXML) {
        // parse error
    } else {
        echo $simpleXML->asXML();
    }
}
35 голосов
/ 26 мая 2009

Трудно определить кодировку.

mb_detect_encoding работает путем угадывания, основываясь на количестве кандидатов, которые вы передаете. В некоторых кодировках определенные последовательности байтов недопустимы, поэтому он может различать различных кандидатов. К сожалению, существует множество кодировок, в которых действительны одни и те же байты (но разные). В этих случаях нет способа определить кодировку; Вы можете реализовать свою собственную логику, чтобы делать предположения в этих случаях. Например, данные, поступающие с японского сайта, могут с большей вероятностью иметь японскую кодировку.

Пока вы имеете дело только с западноевропейскими языками, следует рассмотреть три основные кодировки: utf-8, iso-8859-1 и cp-1252. Поскольку они являются значениями по умолчанию для многих платформ, о них также, скорее всего, сообщают неправильно. Например. если люди используют разные кодировки, они, вероятно, будут откровенны в этом, поскольку в противном случае их программное обеспечение будет очень часто ломаться. Поэтому хорошей стратегией является доверие к поставщику, если только кодировка не указана как одна из этих трех. Вы все равно должны дважды проверить, действительно ли это действительно, используя mb_check_encoding (обратите внимание, что valid не совпадает с , являющимся - один и тот же ввод может быть действительным для многих кодировок). Если это один из них, вы можете использовать mb_detect_encoding, чтобы различать их. К счастью, это довольно детерминистично; Вам просто нужно использовать правильную последовательность обнаружения, которая UTF-8,ISO-8859-1,WINDOWS-1252.

Как только вы обнаружили кодировку, вам нужно преобразовать ее во внутреннее представление (UTF-8 - единственный разумный выбор). Функция utf8_encode преобразует ISO-8859-1 в UTF-8, поэтому ее можно использовать только для этого конкретного типа входа. Для других кодировок используйте mb_convert_encoding.

14 голосов
/ 13 августа 2010

A действительно хороший способ реализации isUTF8 -функции можно найти на php.net :

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}
11 голосов
/ 09 июня 2009

В этой таблице приведены некоторые распространенные предостережения, связанные с обработкой UTF-8 в PHP: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet

Эта функция обнаружения многобайтовых символов в строке также может оказаться полезной ( source ):

<code>
function detectUTF8($string)
{
    return preg_match('%(?:
        [\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);
}
9 голосов
/ 27 июня 2011

Немного подумав, вы сказали, что "ß" должно отображаться как "Â" в вашей базе данных.

Вероятно, это связано с тем, что вы используете базу данных с кодировкой символов latin1 или, возможно, ваше php-mysql соединение установлено неправильно, то есть php считает, что ваш mysql настроен на использование utf-8, поэтому он отправляет данные как utf8 но ваш mysql считает, что php отправляет данные, закодированные как iso-8859-1, поэтому он может еще раз попытаться закодировать отправленные вами данные как utf-8, вызывая такие проблемы.

Взгляните на это, может вам помочь: http://php.net/manual/en/function.mysql-set-charset.php

3 голосов
/ 11 марта 2012

Интересной особенностью mb_detect_encoding и mb_convert_encoding является то, что порядок предлагаемых вами кодировок имеет значение:

// $input is actually UTF-8

mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8");
// ISO-8859-9 (WRONG!)

mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9");
// UTF-8 (OK)

Таким образом, вы можете использовать определенный порядок при указании ожидаемых кодировок. Тем не менее, имейте в виду, что это не надежно.

3 голосов
/ 04 июня 2009

Ваша кодировка выглядит так, как будто вы кодировали в UTF-8 дважды ; то есть из некоторого другого кодирования в UTF-8 и снова в UTF-8. Как будто у вас был iso-8859-1, преобразованный из iso-8859-1 в utf-8 и обработанный новой строкой как iso-8859-1 для другого преобразования в UTF-8.

Вот некоторый псевдокод того, что вы сделали:

$inputstring = getFromUser();
$utf8string = iconv($current_encoding, 'utf-8', $inputstring);
$flawedstring = iconv($current_encoding, 'utf-8', $utf8string);

Вы должны попробовать:

  1. определить кодировку, используя mb_detect_encoding() или что вы хотите использовать
  2. если это UTF-8, конвертировать в iso-8859-1 и повторить шаг 1
  3. наконец, конвертируйте обратно в UTF-8

Это предполагает, что в «среднем» преобразовании вы использовали iso-8859-1. Если вы использовали windows-1252, то конвертируйте в windows-1252 (latin1). Оригинальная исходная кодировка не важна; тот, который вы использовали в ущербном, второе преобразование.

Это мое предположение о том, что произошло; еще очень мало, что вы могли бы сделать, чтобы получить четыре байта вместо одного расширенного байта ASCII.

В немецком языке также используются iso-8859-2 и windows-1250 (latin2).

3 голосов
/ 16 декабря 2011

Вам необходимо проверить кодировку на входе, поскольку ответы могут быть закодированы с различными кодировками.
Я принудительно отправляю весь контент в UTF-8, выполняя обнаружение и перевод, используя следующую функцию:

function fixRequestCharset()
{
  $ref = array( &$_GET, &$_POST, &$_REQUEST );
  foreach ( $ref as &$var )
  {
    foreach ( $var as $key => $val )
    {
      $encoding = mb_detect_encoding( $var[ $key ], mb_detect_order(), true );
      if ( !$encoding ) continue;
      if ( strcasecmp( $encoding, 'UTF-8' ) != 0 )
      {
        $encoding = iconv( $encoding, 'UTF-8', $var[ $key ] );
        if ( $encoding === false ) continue;
        $var[ $key ] = $encoding;
      }
    }
  }
}

Эта процедура превратит все переменные PHP, поступающие с удаленного хоста, в UTF-8.
Или игнорируйте значение, если кодировка не может быть обнаружена или преобразована.
Вы можете настроить его под свои нужды.
Просто вызовите его перед использованием переменных.

2 голосов
/ 01 декабря 2011

Я проверял решения для кодирования со времен AGES, и эта страница, вероятно, завершила годы поиска! Я проверил некоторые из предложений, которые вы упомянули, и вот мои заметки:

Это моя тестовая строка:

это строка "с записью", но мне нужна специальная статья чтобы увидеть их, конвертировать по fùnctìon !! И это все!

Я делаю INSERT, чтобы сохранить эту строку в БД в поле, которое установлено как utf8_general_ci

Кодировка моей страницы - UTF-8

Если я сделаю INSERT просто так, в моей БД у меня есть несколько символов, вероятно, с Марса ... поэтому мне нужно конвертировать их в какой-то "вменяемый" UTF-8. Я пытался utf8_encode(), но все еще инопланетные персонажи вторгались в мою базу данных ...

Поэтому я попытался использовать функцию forceUTF8, размещенную под номером 8, но в БД сохраненная строка выглядит так:

это строка с «написанными и написанными» словами, которые я специально выбрал. чтобы увидеть их, обращайтесь к людям !! И это все!

Итак, собрав еще несколько информационных материалов на этой странице и объединив их с другими информационными материалами на других страницах, я решил свою проблему с помощью этого решения:

$finallyIDidIt = mb_convert_encoding(
  $string,
  mysql_client_encoding($resourceID),
  mb_detect_encoding($string)
);

Теперь в моей базе данных есть строка с правильной кодировкой.

Примечание: Обратите внимание только на функцию mysql_client_encoding! Вам необходимо подключиться к БД, потому что эта функция требует в качестве параметра идентификатор ресурса.

Но я просто перекодирую перед вставкой, так что для меня это не проблема.

Надеюсь, это поможет кому-то, как эта страница помогла мне!

Спасибо всем!

Mauro

...