it-swarm.dev

Detectar codificação e fazer tudo UTF-8

Estou lendo muitos textos de vários feeds RSS e inserindo-os em meu banco de dados.

Naturalmente, existem várias codificações de caracteres diferentes usadas nos feeds, e. UTF-8 e ISO-8859-1.

Infelizmente, às vezes há problemas com as codificações dos textos. Exemplo:

  1. O "ß" em "Futebol" deve ficar assim no meu banco de dados: "Ÿ". Se é um "Ÿ", é exibido corretamente.

  2. Às vezes, o "ß" em "Futebol" fica assim no meu banco de dados: "Ã". Então é exibido erroneamente, é claro.

  3. Em outros casos, o "ß" é salvo como um "ß" - portanto, sem qualquer alteração. Então também é exibido incorretamente.

O que posso fazer para evitar os casos 2 e 3?

Como posso fazer tudo a mesma codificação, preferencialmente UTF-8? Quando devo usar utf8_encode(), quando devo usar utf8_decode() (é claro qual é o efeito, mas quando devo usar as funções?) E quando devo fazer nada com a entrada?

Você pode me ajudar e me dizer como fazer tudo a mesma codificação? Talvez com a função mb_detect_encoding()? Posso escrever uma função para isso? Então meus problemas são:

  1. Como descobrir qual codificação o texto usa?
  2. Como convertê-lo para UTF-8 - seja qual for a codificação antiga?

Uma função como essa funcionaria?

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

Eu testei, mas não funciona. O que há de errado com isso?

287
caw

Se você aplicar utf8_encode() a uma string UTF8, ele retornará uma saída UTF8 truncada.

Eu fiz uma função que resolve todos esses problemas. É chamado Encoding::toUTF8().

Você não precisa saber qual é a codificação das suas strings. Pode ser Latin1 (iso 8859-1), Windows-1252 ou UTF8, ou a string pode ter uma mistura deles. Encoding::toUTF8() irá converter tudo para UTF8.

Eu fiz isso porque um serviço estava me dando um feed de dados todo bagunçado, misturando UTF8 e Latin1 na mesma string.

Uso:

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);

Download:

https://github.com/neitanod/forceutf8

Atualizar:

Eu incluí outra função, Encoding::fixUFT8(), que corrigirá todas as strings UTF8 que pareçam distorcidas.

Uso:

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

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

Exemplos:

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");

irá produzir:

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

Update: Eu transformei a função (forceUTF8) em uma família de funções estáticas em uma classe chamada Encoding. A nova função é Encoding::toUTF8().

341
Sebastián Grignoli

Você primeiro precisa detectar qual codificação foi usada. Ao analisar feeds RSS (provavelmente via HTTP), você deve ler a codificação do parâmetro charset do campo de cabeçalho Content-Type HTTP . Se não estiver presente, leia a codificação do atributo encoding do instrução de processamento XML . Se isso também estiver faltando, se UTF-8 conforme definido na especificação .


Edit Aqui está o que eu provavelmente faria:

Eu usaria cURL para enviar e buscar a resposta. Isso permite que você defina campos de cabeçalho específicos e busque também o cabeçalho de resposta. Depois de buscar a resposta, você precisa analisar a resposta HTTP e dividi-la em cabeçalho e corpo. O cabeçalho deve conter o campo de cabeçalho Content-Type que contém o tipo MIME e (esperançosamente) o parâmetro charset com a codificação/charset também. Caso contrário, analisaremos o PI XML para a presença do atributo encoding e obteremos a codificação a partir dele. Se isso também estiver faltando, as especificações XML definem o uso de UTF-8 como codificação.

$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();
    }
}
72
Gumbo

Detectar a codificação é difícil.

mb_detect_encoding funciona adivinhando, com base em um número de candidatos que você passa. Em algumas codificações, certas seqüências de bytes são inválidas e, portanto, podem distinguir entre vários candidatos. Infelizmente, existem muitas codificações, onde os mesmos bytes são válidos (mas diferentes). Nestes casos, não há como determinar a codificação; Você pode implementar sua própria lógica para fazer suposições nesses casos. Por exemplo, os dados provenientes de um site japonês podem ter maior probabilidade de ter uma codificação em japonês.

Contanto que você lide apenas com idiomas da Europa Ocidental, as três principais codificações a serem consideradas são utf-8, iso-8859-1 e cp-1252. Como esses são padrões para muitas plataformas, eles também são os mais prováveis ​​de serem informados erroneamente. Por exemplo. se as pessoas usarem codificações diferentes, elas provavelmente serão francas sobre isso, já que, caso contrário, o software delas quebraria com muita frequência. Portanto, uma boa estratégia é confiar no provedor, a menos que a codificação seja relatada como um desses três. Você ainda deve verificar que é realmente válido, usando mb_check_encoding (note que válido não é o mesmo que sendo - a mesma entrada pode ser válida para muitas codificações). Se for um desses, você pode usar mb_detect_encoding para distinguir entre eles. Felizmente isso é bastante determinista; Você só precisa usar a sequência de detecção apropriada, que é UTF-8,ISO-8859-1,WINDOWS-1252.

Uma vez que você tenha detectado a codificação, você precisa convertê-la em sua representação interna (UTF-8 é a única escolha sensata). A função utf8_encode transforma ISO-8859-1 em UTF-8, portanto só pode ser usada para esse tipo de entrada específico. Para outras codificações, use mb_convert_encoding.

35
troelskn

A realmente Uma boa maneira de implementar uma função isUTF8- pode ser encontrada em php.net :

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}
14
harpax

Esta cheatsheet lista algumas ressalvas comuns relacionadas ao manuseio do UTF-8 no PHP: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet

Esta função detectando caracteres multibyte em uma string também pode ser útil ( source ):


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);
}
11
miek

Um pouco de atenção, você disse que o "ß" deveria ser exibido como "Ÿ" no seu banco de dados.

Isto é provavelmente porque você está usando um banco de dados com codificação de caracteres latin1 ou possivelmente sua conexão php-mysql está errada, isto é, php acredita que seu mysql está configurado para usar utf-8, então envia dados como utf8, mas seu mysql belives php está enviando dados codificados como iso-8859-1, então ele pode mais uma vez tentar codificar seus dados enviados como utf-8, causando esse tipo de problema.

Dê uma olhada nisso, pode ajudá-lo: http://php.net/manual/en/function.mysql-set-charset.php

9
Krynble

Você precisa testar o charset na entrada, já que as respostas podem ser codificadas com diferentes codificações.
Eu forcei todo o conteúdo a ser enviado para UTF-8 fazendo detecção e tradução usando a seguinte função:

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;
      }
    }
  }
}

Essa rotina transformará todas as variáveis ​​PHP que vêm do host remoto em UTF-8.
Ou ignore o valor se a codificação não puder ser detectada ou convertida.
Você pode personalizá-lo de acordo com suas necessidades.
Basta invocá-lo antes de usar as variáveis.

3
cavila

O interessante sobre mb_detect_encoding e mb_convert_encoding é que a ordem das codificações sugeridas é importante:

// $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)

Então você pode querer usar uma ordem específica ao especificar as codificações esperadas. Ainda assim, tenha em mente que isso não é infalível.

3
Halil Özgür

Sua codificação parece que você codificou em UTF-8 duas vezes; isto é, de alguma outra codificação, em UTF-8 e novamente em UTF-8. Como se você tivesse iso-8859-1, convertido de iso-8859-1 para utf-8 e tratado a nova string como iso-8859-1 para outra conversão em UTF-8.

Aqui está um pseudocódigo do que você fez:

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

Você deveria tentar:

  1. detectar codificação usando mb_detect_encoding() ou o que você quiser usar
  2. se for UTF-8, converta para iso-8859-1 e repita o passo 1
  3. finalmente, converta de volta em UTF-8

Isso é presumir que na conversão "intermediária" você usou iso-8859-1. Se você usou windows-1252, então converta para windows-1252 (latin1). A codificação de origem original não é importante; o que você usou na segunda conversão falho é.

Este é o meu palpite sobre o que aconteceu; há muito pouco mais que você poderia ter feito para obter quatro bytes no lugar de um byte ASCIIestendido.

O idioma alemão também usa iso-8859-2 e windows-1250 (latin2).

3
Ivan Vučica

Trabalhar a codificação de caracteres de feeds RSS parece ser complicado . Mesmo as páginas normais da web geralmente omitem ou mentem sobre sua codificação.

Assim, você pode tentar usar a maneira correta de detectar a codificação e, em seguida, retornar a alguma forma de detecção automática (adivinhação).

2
Kevin ORourke

Eu sei que esta é uma pergunta antiga, mas acho que uma resposta útil nunca é demais. Eu estava tendo problemas com minha codificação entre um aplicativo de desktop, SQLite e variáveis ​​GET/POST. Alguns estariam em UTF-8, alguns em ASCII, e basicamente tudo se estragaria quando personagens estrangeiros se envolvessem.

Aqui está a minha solução. Ele scrubs seu GET/POST/REQUEST (eu omitido cookies, mas você poderia adicioná-los, se desejar) em cada carga de página antes do processamento. Funciona bem em um cabeçalho. PHP lançará avisos se não puder detectar a codificação de origem automaticamente, então esses avisos são suprimidos com @'s.

//Convert everything in our vars to UTF-8 for playing Nice with the database...
//Use some auto detection here to help us not double-encode...
//Suppress possible warnings with @'s for when encoding cannot be detected
try
{
    $process = array(&$_GET, &$_POST, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = $v;
                $process[] = &$process[$key][@mb_convert_encoding($k,'UTF-8','auto')];
            } else {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = @mb_convert_encoding($v,'UTF-8','auto');
            }
        }
    }
    unset($process);
}
catch(Exception $ex){}
2
jocull

Eu estava verificando soluções para codificação desde AGES, e esta página é provavelmente a conclusão de anos de pesquisa! Eu testei algumas das sugestões que você mencionou e aqui estão minhas anotações:

Esta é minha string de teste:

este é um "wròng wrìtten" cadeia de caracteres que eu nè a pù 'sòme' specials para ver thèm, convertèd by fùnctìon !! & é isso aí!

Eu faço um INSERT para salvar esta string em um banco de dados em um campo que é definido como utf8_general_ci

Charset da minha página é UTF-8

Se eu fizer um INSERT assim, no meu DB eu tenho alguns chars provavelmente vindos de Marte ... então eu preciso convertê-los em alguns "sãos" UTF-8. Eu tentei utf8_encode() mas ainda aliens chars estavam invadindo meu banco de dados ...

Então eu tentei usar a função forceUTF8 postada no número 8, mas no DB a string salva se parece com isso:

este é um "wrúng wrÃtten" string bà ¥ neu para pôs 'sÂÂme especial para ver tèm, convertàde fà ¢ nctìon !! & é isso aí!

Então, coletando mais informações nesta página e mesclando-as com outras informações em outras páginas, resolvi meu problema com essa solução:

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

Agora no meu banco de dados eu tenho minha string com codificação correta.

NOTA: Somente nota para cuidar está na função mysql_client_encoding! Você precisa estar conectado ao banco de dados porque essa função deseja um ID de recurso como parâmetro.

Mas bem, eu só faço isso re-codificação antes do meu INSERT então para mim não é um problema.

Espero que isso ajude alguém como esta página me ajudou!

Obrigado a todos!

Mauro

2
Mauro

É simples: quando você pega algo que não é UTF8, você deve ENCODIR isso em utf8.

Então, quando você está buscando um determinado feed, o ISO-8859-1 analisa isso por meio do utf8_encode.

No entanto, se você estiver buscando um feed UTF8, não precisará fazer nada.

2
Seb

php.net/ mb_detect_encoding

echo mb_detect_encoding($str, "auto");

ou

echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");

eu realmente não sei quais são os resultados, mas eu sugiro que você pegue alguns de seus feeds com diferentes codificações e tente se mb_detect_encoding funcionar ou não.

atualização
auto é a abreviação de "ASCII, JIS, UTF-8, EUC-JP e SJIS". ele retorna o conjunto de caracteres detectado, que você pode usar para converter a string para utf-8 com iconv .

<?php
function convertToUTF8($str) {
    $enc = mb_detect_encoding($str);

    if ($enc && $enc != 'UTF-8') {
        return iconv($enc, 'UTF-8', $str);
    } else {
        return $str;
    }
}
?>

eu não testei, então não há garantia. e talvez haja um jeito mais simples.

1
stefs

@harpax que funcionou para mim. No meu caso, isso é bom o suficiente:

if (isUTF8($str)) { 
    echo $str; 
}
else
{
    echo iconv("ISO-8859-1", "UTF-8//TRANSLIT", $str);
}
1
PJ Brunet

Ÿ é Mojibake para ß. No seu banco de dados, você pode ter hexadecimal

DF if the column is "latin1",
C39F if the column is utf8 -- OR -- it is latin1, but "double-encoded"
C383C5B8 if double-encoded into a utf8 column

Você deve não usar qualquer função de codificação/decodificação no PHP; em vez disso, você deve configurar o banco de dados e a conexão a ele corretamente.

Se o MySQL estiver envolvido, veja: Problema com os caracteres utf8; o que eu vejo não é o que eu armazenei

0
Rick James

Depois de classificar seus scripts php, não se esqueça de dizer ao mysql qual charset você está passando e gostaria de rececionar.

Exemplo: definir o conjunto de caracteres utf8

Transmitir dados utf8 para uma tabela latin1 em uma sessão de E/S latin1 fornece aqueles péssimos pássaros. Eu vejo isso todos os dias em lojas de oscommerce. Voltar e quarto pode parecer certo. Mas o phpmyadmin mostrará a verdade. Ao dizer ao mysql qual charset você está passando, ele irá manipular a conversão dos dados do mysql para você.

Como recuperar dados mysql scrambled existentes é outro tópico para discutir. :)

0
tim

Obter codificação de cabeçalhos e convertê-lo em utf-8.

$post_url='http://website.domain';

/// Get headers ////////////////////////////////////////////////////////////
function get_headers_curl($url) 
{ 
    $ch = curl_init(); 

    curl_setopt($ch, CURLOPT_URL,            $url); 
    curl_setopt($ch, CURLOPT_HEADER,         true); 
    curl_setopt($ch, CURLOPT_NOBODY,         true); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_TIMEOUT,        15); 

    $r = curl_exec($ch); 
    return $r; 
}
$the_header = get_headers_curl($post_url);
/// check for redirect /////////////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
    $arr = explode('Location:', $the_header);
    $location = $arr[1];

    $location=explode(chr(10), $location);
    $location = $location[0];

$the_header = get_headers_curl(trim($location));
}
/// Get charset /////////////////////////////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
    $arr = explode('charset=', $the_header);
    $charset = $arr[1];

    $charset=explode(chr(10), $charset);
    $charset = $charset[0];
    }
///////////////////////////////////////////////////////////////////////////////
// echo $charset;

if($charset && $charset!='UTF-8') { $html = iconv($charset, "UTF-8", $html); }
0
Arsen

A resposta mais votada não funciona. Aqui é meu e espero que ajude.

function toUTF8($raw) {
    try{
        return mb_convert_encoding($raw, "UTF-8", "auto"); 
    }catch(\Exception $e){
        return mb_convert_encoding($raw, "UTF-8", "GBK"); 
    }
}
0
fzyzcjy

Eu acho solução aqui http://deer.org.ua/2009/10/06/1/

class Encoding
{
    /**
     * http://deer.org.ua/2009/10/06/1/
     * @param $string
     * @return null
     */
    public static function detect_encoding($string)
    {
        static $list = ['utf-8', 'windows-1251'];

        foreach ($list as $item) {
            try {
                $sample = iconv($item, $item, $string);
            } catch (\Exception $e) {
                continue;
            }
            if (md5($sample) == md5($string)) {
                return $item;
            }
        }
        return null;
    }
}

$content = file_get_contents($file['tmp_name']);
$encoding = Encoding::detect_encoding($content);
if ($encoding != 'utf-8') {
    $result = iconv($encoding, 'utf-8', $content);
} else {
    $result = $content;
}

Eu acho que @ é uma decisão ruim, e faça algumas mudanças na solução de deer.org.ua;

0
Paul