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

caw*_*caw 293 php encoding utf-8 character-encoding

我正在从各种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;
}
Run Code Online (Sandbox Code Playgroud)

我已经测试了它,但它不起作用.它出什么问题了?

Seb*_*oli 352

如果您应用于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);
Run Code Online (Sandbox Code Playgroud)

下载:

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);
Run Code Online (Sandbox Code Playgroud)

例子:

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");
Run Code Online (Sandbox Code Playgroud)

将输出:

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Run Code Online (Sandbox Code Playgroud)

更新:我已经将函数(forceUTF8)转换为一个名为的类的静态函数族Encoding.新功能是Encoding::toUTF8().

  • *"你不需要知道字符串的编码是什么."* - 我非常不同意.猜测和尝试可能会有效,但是你总会迟早会遇到边缘情况. (26认同)
  • 它假设ISO-8859-1,答案已经说明了这一点.forceUTF8()和utf8_encode()之间的唯一区别是forceUTF8()识别UTF8字符并保持不变. (4认同)
  • 如何将非UTF8字符转换为UTF8,而不知道开头的无效字符的编码是什么? (3认同)
  • 我完全同意.事实上,我并不是说一般来说,只要解释一下这个课程可能会帮助你,如果那是你碰巧遇到的情况. (2认同)

Gum*_*mbo 72

首先必须检测已使用的编码.当您正在解析RSS提要(可能通过HTTP)时,您应该从HTTP标头字段charset参数中读取编码.如果不存在,请从XML处理指令的属性中读取编码.如果缺少,请使用规范中定义的UTF-8.Content-Typeencoding


编辑    这是我可能会做的:

我使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 再说一遍:那不是你的问题.制定标准以避免此类麻烦.如果其他人不遵循他们,这是他们的问题,而不是你的问题. (25认同)

tro*_*skn 36

检测编码很难.

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

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

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

  • 我刚刚看到:mb-detect-encoding()没用.它仅支持UTF-8,UTF-7,ASCII,EUC-JP,SJIS,eucJP-win,SJIS-win,JIS和ISO-2022-JP.对我来说最重要的是ISO-8859-1和WINDOWS-1252,不受支持.所以我不能使用mb-detect-encoding(). (7认同)

har*_*pax 14

php.net上可以找到实现函数的一种非常好的方法:isUTF8

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,这仅在字符串仅由ISO-8859-1中包含的字符组成时才有效.但这可行:@iconv('utf-8','utf-8 // IGNORE',$ str)== $ str (15认同)
  • 只是为了说明这种情况的严重性:ISO 8859-1 中正好有 191 个可打印字符;Unicode 13 定义了大约 140000 个。因此,如果您选择一个随机 Unicode 字符,将其正确编码为 UTF-8,并将其传递给此函数,则此函数有超过 99% 的机会错误地返回 false。如果您认为这些字符很晦涩,请注意 ISO 8859-1 没有欧元符号,因此 `isUTF8('€')` 将属于 99% 的字符。 (6认同)
  • `mb_check_encoding($string, 'UTF-8')` (2认同)

mie*_*iek 12

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

此函数检测字符串中的多字节字符也可能有用():


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

  • 我认为这不能正常工作:echo detectUTF8('3٣3'); #1 (2认同)

Kry*_*ble 9

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

这可能是因为你正在使用带有latin1字符编码的数据库或者你的php-mysql连接设置错误,这就是,php认为你的mysql设置为使用utf-8,所以它发送数据为utf8,但是你的mysql belives php正在发送编码为iso-8859-1的数据,因此它可能会再次尝试将您发送的数据编码为utf-8,从而导致这种麻烦.

看看这个,可以帮到你:http://php.net/manual/en/function.mysql-set-charset.php


Iva*_*ica 5

您的编码看起来像是两次编码为 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);
Run Code Online (Sandbox Code Playgroud)

你应该试试:

  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 (Latin-2)。