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

我从各种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;
}

我已经测试过了,但是不行。有什么问题吗?

427948 次浏览

这很简单:当你得到一些不是UTF-8的东西时,你必须编码那个 UTF-8。

因此,当你获取某个ISO 8859-1的提要时,通过utf8_encode解析它。

但是,如果您正在获取UTF-8提要,则不需要做任何事情。

计算出RSS提要的字符编码似乎是复杂的。即使是普通的网页也经常忽略或谎报它们的编码。

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

mb_detect_encoding:

echo mb_detect_encoding($str, "auto");

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

我真的不知道结果是什么,但我建议你只是采取一些不同编码的提要,并尝试是否mb_detect_encoding工作。

汽车是“ASCII,JIS,UTF-8,EUC-JP, sjs”的缩写。它返回检测到的字符集,您可以使用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;
}
}
?>

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

检测编码是困难的。

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

首先必须检测使用了什么编码。在解析RSS提要时(可能通过HTTP),应该从Content-Type HTTP报头字段charset参数中读取编码。如果不存在,则从XML处理指令encoding属性中读取编码。如果也没有,使用规范中定义的UTF-8


以下是我可能会做的:

我将使用旋度来发送和获取响应。这允许您设置特定的报头字段并获取响应报头。获取响应后,必须解析HTTP响应并将其分为头和体。标头应该包含Content-Type标头字段,其中包含MIME类型和(希望)带有编码/字符集的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();
}
}

你的编码看起来像你编码到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 - 2windows - 1250(拉丁语-2)。

这个备备单列出了PHP中与UTF-8处理相关的一些常见注意事项: http://developer.loftdigital.com/blog/php-utf-8-cheatsheet < / > < / p >

这个函数检测字符串中的多字节字符也可能是有帮助的():

以前< p > < > <代码> 函数detectUTF8(字符串)美元 { 返回preg_match (' % (?: [\xC2-\xDF][\x80-\xBF] #非超长2字节 |\xE0[\xA0-\xBF][\x80-\xBF] #不包括超长 | [\ xE1 - \ xEC \ xEE \ xEF] [\ x80 - \ xBF]{2} # 3字节 |\xED[\x80-\x9F][\x80-\xBF] #不包括代理 |\xF0[\x90-\xBF][\x80-\xBF]{2} #飞机1-3 |[\xF1-\xF3][\x80-\xBF]{3} #飞机4-15 |\xF4[\x80-\x8F][\x80-\xBF]{2} #飞机 ) + % x”, $ string); } < / pre > < /代码> < / p >

我知道这是一个老问题,但我认为一个有用的答案不会有坏处。我在桌面应用程序、SQLite和GET/POST变量之间的编码有问题。有些会使用UTF-8,有些会使用ASCII,当涉及到外国字符时,基本上所有事情都会搞砸。

这是我的解决方案。在处理之前,它会在每个页面加载时擦除GET/POST/REQUEST(我省略了cookie,但如果需要可以添加它们)。它在标题中工作得很好。如果PHP不能自动检测到源编码,它将抛出警告,因此这些警告将被@'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){}

可以在php.net中找到实现__abc0函数的真的好方法:

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

如果你将utf8_encode()应用于一个已经UTF-8的字符串,它将返回乱码的UTF-8输出。

我做了一个函数来解决所有这些问题。它被称为Encoding::toUTF8()

你不需要知道字符串的编码是什么。它可以是latin (ISO 8859 - 1)windows - 1252或UTF-8),或者字符串可以是它们的组合。Encoding::toUTF8()将所有内容转换为UTF-8。

我这样做是因为一个服务给了我一个混乱的数据提要,在同一个字符串中混合了UTF-8和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(),它将修复每个看起来乱码的UTF-8字符串。

用法:

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

提醒一下。您说在您的数据库中“ß”应该显示为“Ÿ”。

这可能是因为你正在使用一个Latin-1字符编码的数据库,或者你的PHP-MySQL连接设置错误,也就是说,P认为你的MySQL设置为使用UTF-8,所以它发送数据为UTF-8,但你的MySQL认为PHP发送的数据编码为ISO 8859-1,所以它可能再次尝试将你发送的数据编码为UTF-8,造成这种麻烦。

看看mysql_set_charset。它可能对你有帮助。

harpax的回答对我有用。对我来说,这就足够了:

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

当你试着掌握多种语言时,比如日语和韩语,你可能会遇到麻烦。

带有'auto'参数的mb_convert_encoding不能很好地工作。设置mb_detect_order('ASCII,UTF-8,JIS,EUC- jp,SJIS,EUC- kr,UHC')没有帮助,因为它会错误地检测EUC-*。

我的结论是,只要输入字符串来自HTML,它就应该在元元素中使用“字符集”。我使用简单HTML DOM解析器是因为它支持无效的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);
}

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

这是我的测试字符串:

这是一个"wròng wrìtten"字符串bùt I nèed到pù 'sòme'特殊 Chàrs看thèm, convertèd看fùnctìon!!,就是这样!< / p >

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

我的页面的字符集是UTF-8。

如果我像这样做一个INSERT,在我的数据库中,我有一些字符可能来自火星…

所以我需要把它们转换成“sane"utf - 8。我尝试utf8_encode(),但仍然外星人字符入侵我的数据库…

所以我尝试使用函数forceUTF8发布在第8号,但在数据库中保存的字符串看起来像这样:

这是一个"wròng wrà "string bùt I nèed to pù'sòme' special Chà rs看thèm, convertèd由fùnctà on!!,就是这样!< / p >

所以在这个页面上收集更多的信息,并将它们与其他页面上的其他信息合并,我用这个解决方案解决了我的问题:

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

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

注意:

唯一需要注意的是函数mysql_client_encoding! 你需要连接到数据库,因为这个函数需要一个资源ID作为参数

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

您需要在输入上测试字符集,因为响应可以用不同的编码进行编码。

我强迫所有的内容被发送到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。

如果无法检测或转换编码,则忽略该值。

您可以根据自己的需要定制它。

只需在使用变量之前调用它。

整理完PHP脚本后,不要忘记告诉MySQL你要传递的字符集和你想要接收的字符集。

例如:设置为UTF-8

在Latin 1 I/O会话中将UTF-8数据传递给拉丁1表会导致这些讨厌的鸟脚。我每隔一天就会在OsCommerce商店看到这个。后面和第四个似乎是对的。但phpMyAdmin将显示真相。通过告诉MySQL你正在传递什么字符集,它将为你处理MySQL数据的转换。

如何恢复现有的乱码MySQL数据是另一个问题。:)

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

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

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

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

我有同样的问题与phpQuery (iso - 8859 - 1而不是utf - 8)和这个hack帮助我:

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

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

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

ŸßMojibake。在你的数据库中,你可能有以下十六进制值之一(使用SELECT HEX(col)...)来找出):

  • DF如果列是"latin1"
  • C39F如果列为utf8—OR—它是latin1,但是“双编码”;
  • C383C5B8如果被双编码为utf8列

你应该使用PHP中的任何编码/解码函数;相反,您应该正确地设置数据库和到数据库的连接。

如果涉及MySQL,请参见: UTF-8字符错误;我看到的不是我存储的东西

我在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对解决方案做了一些更改。

对于中文字符,通常采用GBK编码。此外,在测试时,投票最多的答案不成立。这里有一个简单的修复方法,让它也能正常工作:

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

注:这个解决方案是在2017年编写的,应该可以修复当时PHP的问题。我还没有测试最新的PHP是否已经正确理解auto

不带auto的Try

那就是:

mb_detect_encoding($text)

而不是:

mb_detect_encoding($text, 'auto')

更多信息可以在这里找到:mb_detect_encoding

试着用这个…所有不是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);