检测编码并使所有内容UTF-8

时间:2009-05-26 13:50:35

标签: php encoding utf-8 character-encoding

我正在从各种RSS源中读出大量文本并将其插入我的数据库。

当然,在Feed中使用了几种不同的字符编码,例如: UTF-8和ISO-8859-1。

不幸的是,文本的编码有时会出现问题。例如:

  1. “Fußball”中的“ß”应该在我的数据库中显示如下:“Ÿ”。如果是“”,则会正确显示。

  2. 有时,“Fußball”中的“ß”在我的数据库中看起来像这样:“ß”。当然,它显示错误。

  3. 在其他情况下,“ß”保存为“ß” - 因此不做任何更改。然后它也显示错误。

  4. 我可以做些什么来避免案例2和3?

    如何使所有内容编码相同,最好是UTF-8?什么时候我必须使用utf8_encode(),什么时候必须使用utf8_decode()(很清楚效果是什么,但什么时候必须使用这些功能?)什么时候我必须对输入做什么?

    你能帮助我并告诉我如何使一切编码相同吗?也许功能mb_detect_encoding()?我能为此写一个函数吗?所以我的问题是:

    1. 如何找出文本使用的编码?
    2. 如何将其转换为UTF-8 - 无论旧编码是什么?
    3. 这样的功能会起作用吗?

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

      我已经测试了它,但它不起作用。怎么了?

25 个答案:

答案 0 :(得分:339)

如果您将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()

答案 1 :(得分:72)

首先必须检测已使用的编码。当您正在解析RSS源(可能通过HTTP)时,您应该从Content-Type HTTP header fieldcharset参数中读取编码。如果不存在,请从XML processing instructionencoding属性中读取编码。如果那也不见了,use UTF-8 as defined in the specification


编辑以下是我可能会做的事情:

我会使用cURL来发送和获取响应。这允许您设置特定的头字段并获取响应头。获取响应后,您必须解析HTTP响应并将其拆分为标题和正文。然后,标头应包含Content-Type标头字段,其中包含MIME类型,并且(希望)charset参数也包含encoding / 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();
    }
}

答案 2 :(得分:35)

检测编码很难。

mb_detect_encoding根据您传递的一些候选人进行猜测。在某些编码中,某些字节序列是无效的,因此它可以区分各种候选。不幸的是,有很多编码,其中相同的字节是有效的(但不同)。在这些情况下,无法确定编码;在这些情况下,您可以实现自己的逻辑来猜测。例如,来自日本网站的数据可能更有可能采用日语编码。

只要您只处理西欧语言,要考虑的三个主要编码是utf-8iso-8859-1cp-1252。由于这些是许多平台的默认设置,因此它们也最有可能被错误地报告。例如。如果人们使用不同的编码,他们可能会坦诚相待,因为否则他们的软件会经常破坏。因此,一个好的策略是信任提供者,除非编码被报告为这三者之一。您仍然应该使用mb_check_encoding双重检查它确实有效(请注意有效 不同 - 相同的输入可能对许多编码有效)。如果是其中之一,则可以使用mb_detect_encoding来区分它们。幸运的是,这是相当确定的;您只需要使用正确的检测序列,即UTF-8,ISO-8859-1,WINDOWS-1252

一旦检测到编码,您需要将其转换为内部表示(UTF-8是唯一合理的选择)。函数utf8_encodeISO-8859-1转换为UTF-8,因此它只能用于特定的输入类型。对于其他编码,请使用mb_convert_encoding

答案 3 :(得分:13)

真正实现isUTF8 - 函数的好方法可以在php.net上找到:

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

答案 4 :(得分:11)

此备忘单列出了与PHP中UTF-8处理相关的一些常见警告: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet

此函数检测字符串中的多字节字符也可能有用(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);
}

答案 5 :(得分:9)

有点抬头,你说“ß”应该在你的数据库中显示为“Ÿ”。

这可能是因为你正在使用带有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

答案 6 :(得分:3)

关于mb_detect_encodingmb_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)

因此,您可能希望在指定预期编码时使用特定顺序。不过,请记住,这不是万无一失的。

答案 7 :(得分:3)

您的编码看起来像是编码为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
  4. 假设在“中间”转换中你使用的是iso-8859-1。如果您使用的是Windows-1252,则转换为windows-1252(latin1)。原始源编码并不重要;你在有缺陷的第二次转换中使用的是。

    这是我对发生的事情的猜测;除了一个扩展的ASCII字节之外,你还可以做很多事情来获得四个字节。

    德语也使用iso-8859-2和windows-1250(latin2)。

答案 8 :(得分:3)

您需要在输入上测试字符集,因为响应可以使用不同的编码进行编码。
我通过使用以下功能进行检测和翻译,强制将所有内容发送到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。
如果无法检测或转换编码,请忽略该值。
您可以根据需要进行定制。
只需在使用变量之前调用它。

答案 9 :(得分:2)

计算RSS提要的字符编码似乎是complicated。即使是普通的网页,也经常会忽略或说谎他们的编码。

因此,您可以尝试使用正确的方法来检测编码,然后再回到某种形式的自动检测(猜测)。

答案 10 :(得分:2)

我知道这是一个较老的问题,但我认为一个有用的答案永远不会伤害。我在桌面应用程序,SQLite和GET / POST变量之间遇到了编码问题。有些是UTF-8,有些是ASCII,基本上一切都会在外国人参与时被搞砸。

这是我的解决方案。在处理之前,它会在每个页面加载时擦除您的GET / POST / REQUEST(我省略了cookie,但您可以根据需要添加它们)。它在标题中运行良好。如果PHP无法自动检测源编码,则会抛出警告,因此使用@来抑制这些警告。

//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){}

答案 11 :(得分:2)

这很简单:当你得到的东西不是UTF8时,你必须ENCODE INTO utf8。

所以,当你拿到一个ISO-8859-1的某个feed通过utf8_encode解析它时。

但是,如果您要提取UTF8 Feed,则无需执行任何操作。

答案 12 :(得分:2)

我正在检查自AGES以来的编码解决方案,这个页面可能是多年搜索的结论! 我测试了你提到的一些建议,这是我的笔记:

这是我的测试字符串:

  

这是一个“wròngwrìtten”字符串bùtinèedtopù'sòme'pecial   chàrs以查看thèm,convertèdbyfùnctìon!! &安培;就是这样!

我执行INSERT将此字符串保存在设置为utf8_general_ci的字段中的数据库

我的网页的字符集是UTF-8

如果我像这样执行INSERT,在我的数据库中我有一些可能来自火星的字符...... 所以我需要将它们转换成一些“理智”的UTF-8。 我试过了utf8_encode()但是外星人的角色还在侵犯我的数据库...

所以我尝试使用在数字8上发布的函数forceUTF8但在DB上保存的字符串看起来像这样:

  

这是一个“wròngwrätten”字符串bùtInèedtopù'sòme'special   chÃrs看到thèm,转换为fùnctÃ-on !! &安培;就是这样!

因此,在此页面上收集更多信息并将其与其他页面上的其他信息合并,我解决了此解决方案的问题:

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

现在在我的数据库中,我的字符串编码正确。

注意: 只注意功能mysql_client_encoding! 您需要连接到DB,因为此函数需要资源ID作为参数。

但是,我只是在我的INSERT之前重新编码,所以对我来说不是问题。

我希望这会帮助像这个页面的人帮助我!

感谢大家!

莫罗

答案 13 :(得分:1)

@harpax对我有用。就我而言,这已经足够了:

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

答案 14 :(得分:1)

php.net / mb_detect_encoding

echo mb_detect_encoding($str, "auto");

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

我真的不知道结果是什么,但我建议您只使用不同编码的一些Feed,并尝试mb_detect_encoding是否有效。

<强>更新
auto是“ASCII,JIS,UTF-8,EUC-JP,SJIS”的缩写。它返回检测到的字符集,您可以使用该字符串将字符串转换为带有iconv的utf-8。

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

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

我没有测试过,所以没有保证。也许有一种更简单的方法。

答案 15 :(得分:0)

在整理出你的php脚本之后,不要忘记告诉mysql你传递的是什么字符集并且想要接受。

示例:设置字符集utf8

在latin1 I / O会话中将utf8数据传递给latin1表会产生令人讨厌的鸟类。我每隔一天在oscommerce商店看到这个。回到第四,看起来似乎是对的。但phpmyadmin将显示真相。通过告诉mysql你传递的是什么字符集,它将为你处理mysql数据的转换。

如何恢复现有的乱码mysql数据是另一个要讨论的线程。 :)

答案 16 :(得分:0)

此版本适用于德语,但您可以修改$ CHARSETS和$ TEACHERS

class CharsetDetector
{
private static $CHARSETS = array(
"ISO_8859-1",
"ISO_8859-15",
"CP850"
);
private static $TESTCHARS = array(
"€",
"ä",
"Ä",
"ö",
"Ö",
"ü",
"Ü",
"ß"
);
public static function convert($string)
{
    return self::__iconv($string, self::getCharset($string));
}
public static function getCharset($string)
{
    $normalized = self::__normalize($string);
    if(!strlen($normalized))return "UTF-8";
    $best = "UTF-8";
    $charcountbest = 0;
    foreach (self::$CHARSETS as $charset) {
        $str = self::__iconv($normalized, $charset);
        $charcount = 0;
        $stop   = mb_strlen( $str, "UTF-8");

        for( $idx = 0; $idx < $stop; $idx++)
        {
            $char = mb_substr( $str, $idx, 1, "UTF-8");
            foreach (self::$TESTCHARS as $testchar) {

                if($char == $testchar)
                {

                    $charcount++;
                    break;
                }
            }
        }
        if($charcount>$charcountbest)
        {
            $charcountbest=$charcount;
            $best=$charset;
        }
        //echo $text."<br />";
    }
    return $best;
}
private static function __normalize($str)
{

$len = strlen($str);
$ret = "";
for($i = 0; $i < $len; $i++){
    $c = ord($str[$i]);
    if ($c > 128) {
        if (($c > 247)) $ret .=$str[$i];
        elseif ($c > 239) $bytes = 4;
        elseif ($c > 223) $bytes = 3;
        elseif ($c > 191) $bytes = 2;
        else $ret .=$str[$i];
        if (($i + $bytes) > $len) $ret .=$str[$i];
        $ret2=$str[$i];
        while ($bytes > 1) {
            $i++;
            $b = ord($str[$i]);
            if ($b < 128 || $b > 191) {$ret .=$ret2; $ret2=""; $i+=$bytes-1;$bytes=1; break;}
            else $ret2.=$str[$i];
            $bytes--;
        }
    }
}
return $ret; 
}
private static function __iconv($string, $charset)
{
    return iconv ( $charset, "UTF-8" , $string );
}
}

答案 17 :(得分:0)

从标头中获取编码并将其转换为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); }

答案 18 :(得分:0)

Ÿß的Mojibake。在您的数据库中,您可能有十六进制

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

你应该在PHP中使用任何编码/解码功能;相反,您应该正确设置数据库及其连接。

如果涉及MySQL,请参阅:Trouble with utf8 characters; what I see is not what I stored

答案 19 :(得分:0)

我在这里找到解决方案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;
}

我认为 @ 是错误的决定,并对来自deer.org.ua的解决方案进行了一些更改;

答案 20 :(得分:0)

投票最多的答案不起作用。这是我的,希望它有所帮助。

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

答案 21 :(得分:-1)

尝试使用这个...每个非 UTF-8 的文本都将被翻译。

function is_utf8($str) {
    return (bool) preg_match('//u', $str);
}

$myString = "Fußball";

if(!is_utf8($myString)){
    $myString = utf8_encode($myString);
}

// or 1 line version ;) 
$myString = !is_utf8($myString) ? utf8_encode($myString) : trim($myString);

答案 22 :(得分:-1)

尝试不使用&#39;自动&#39;

那是:

mb_detect_encoding($text)

而不是:

mb_detect_encoding($text, 'auto')

可在此处找到更多信息:mb_detect_encoding

答案 23 :(得分:-1)

我遇到了与 phpQuery ISO-8859-1 而不是 UTF-8 )相同的问题,这个黑客帮助了我:

$html = '<?xml version="1.0" encoding="UTF-8" ?>' . $html;

mb_internal_encoding('UTF-8')phpQuery::newDocumentHTML($html, 'utf-8')mbstring.internal_encoding和其他操作没有任何效果。

答案 24 :(得分:-1)

当您尝试处理日语和韩语等多种语言时,您可能遇到麻烦。带有'auto'参数的mb_convert_encoding不能正常工作。设置mb_detect_order('ASCII,UTF-8,JIS,EUC-JP,SJIS,EUC-KR,UHC')没有用,因为它会错误地检测EUC- *。

我得出结论,只要输入字符串来自HTML,就应该在元素元素中使用“charset”。我使用Simple HTML DOM Parser因为它支持无效的HTML。

以下代码段从网页中提取标题元素。如果您想转换整个页面,那么您可能想要删除一些行。

<?php
require_once 'simple_html_dom.php';

echo convert_title_to_utf8(file_get_contents($argv[1])), PHP_EOL;

function convert_title_to_utf8($contents)
{
    $dom = str_get_html($contents);
    $title = $dom->find('title', 0);
    if (empty($title)) {
        return null;
    }
    $title = $title->plaintext;
    $metas = $dom->find('meta');
    $charset = 'auto';
    foreach ($metas as $meta) {
        if (!empty($meta->charset)) { // html5
            $charset = $meta->charset;
        } else if (preg_match('@charset=(.+)@', $meta->content, $match)) {
            $charset = $match[1];
        }
    }
    if (!in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) {
        $charset = 'auto';
    }
    return mb_convert_encoding($title, 'UTF-8', $charset);
}