PHP:在不知道原始字符集的情况下将任何字符串转换为 UTF-8,或者至少尝试一下

Posted

技术标签:

【中文标题】PHP:在不知道原始字符集的情况下将任何字符串转换为 UTF-8,或者至少尝试一下【英文标题】:PHP: Convert any string to UTF-8 without knowing the original character set, or at least try 【发布时间】:2011-12-20 05:50:00 【问题描述】:

我有一个处理来自世界各地的客户的应用程序,当然,我希望进入我的数据库的所有内容都采用 UTF-8 编码。

对我来说主要问题是我不知道任何字符串的源将是什么编码 - 它可能来自文本框(使用<form accept-charset="utf-8"> 仅在用户实际提交表单时才有用),或者它可能来自上传的文本文件,所以我真的无法控制输入。

我需要一个函数或类,以确保进入我的数据库的内容尽可能采用 UTF-8 编码。我试过iconv(mb_detect_encoding($text), "UTF-8", $text); 但这有问题(如果输入是“未婚夫”,则返回“未婚夫”)。我已经尝试了很多东西=/

对于文件上传,我喜欢让最终用户指定他们使用的编码,并向他们展示输出的预览,但这无助于对付讨厌的黑客(事实上,它可以让他们的生活更轻松)。

我已阅读有关该主题的其他 SO 问题,但它们似乎都有细微的差异,例如“我需要解析 RSS 提要”或“我从网站上抓取数据”(或者,实际上,“你不能”)。

但一定有什么东西至少有好的试试

【问题讨论】:

根据定义基本上不可能完全正确,实际上猜测未知编码的成功率并不高。可以使用启发式方法,但正确率低于 100%,具体取决于材料远低于 100%。你需要意识到这一点。也许这里有人至少可以建议一个具有良好启发式的库。 当然,我知道没有完美的解决方案 - 因此,我们希望至少能顺利进行。 这可能会有所帮助:***.com/q/505562/642173 您是否尝试过使用UTF-8//IGNORE 作为iconv 中的第二个参数? 是的,这就是我最终所做的。显然,这并不完美,因为“未婚妻”变成了“未婚夫”,但肯定更好。为什么 TRANSLIT 不起作用? 【参考方案1】:

您所要求的非常困难。如果可能,让用户指定编码是最好的。以这种方式预防攻击应该不会变得更容易或更难。

但是,您可以尝试这样做:

iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);

将其设置为严格可能会帮助您获得更好的结果。

【讨论】:

请查看您的 php 发行版中的 mb_detect_encoding 源代码(此处某处:ext/mbstring/libmbfl/mbfl/mbfl_ident.c)。此功能根本无法正常工作。对于某些编码,它甚至具有“return true”,哈哈。其他在 Ctrl+c Ctrl+v 函数中。那是因为如果没有某种字典或统计方法(如我的),您将无法检测编码。 按照我的理解,mb_detect_encoding 遍历提供的编码列表,并接受第一个在字符串中没有无效字节序列的编码......对于没有无效字节序列的编码例如 ISO-8859-1,它总是正确的。没有“智能”启发式方法,结果会因您传递的编码列表(和顺序)而有很大差异。 这似乎对我有用。我的用户使用 tinymce 在 utf8 页面上提交文本,但由于某些未知原因,非 utf8 字符有时会出现在数据库中。这解决了它,所以非常感谢你。 @Jeff Day - 谢谢。请原谅我的无知,“设置为严格”是什么意思? [Jeff Day] 正在发送 mb_detect_order(),尽管它是此参数的默认值,因为他想将严格编码检测设置为 true(第三个参数):)【参考方案2】:

您可以设置一组指标来尝试猜测正在使用哪种编码。同样,并不完美,但可以从 mb_detect_encoding() 中捕捉到一些缺失。

【讨论】:

是的,说到mb_detect_encoding() 未命中,你认为我的回答在撒哈拉沙漠的夏天有滚雪球的机会吗?【参考方案3】:

您可能已经尝试过,但为什么不直接使用 mb_convert_encoding 函数呢?它将尝试自动检测所提供文本的字符集,或者您可以将其传递给列表。

另外,我尝试运行:

$text = "fiancée";
echo mb_convert_encoding($text, "UTF-8");
echo "<br/><br/>";
echo iconv(mb_detect_encoding($text), "UTF-8", $text);

两者的结果相同。您如何看到您的文本被截断为“未婚夫”?是在数据库中还是在浏览器中?

【讨论】:

在数据库中,似乎 - 我刚刚尝试过您的代码,我同意。 检查以确保您在表/列上定义的排序规则也是 UTF-8。 @AlexeyGerasimov 我想我真的需要调查iconv。我尝试了一种几乎纯粹的 mb_* 方式。你怎么看?【参考方案4】:

在祖国俄罗斯,我们有 4 种流行的编码,所以你的问题在这里很受欢迎。

仅通过符号的字符代码无法检测编码,因为代码页相交。一些不同语言的代码页甚至有完整的交集。所以,我们需要另一种方法

使用未知编码的唯一方法是使用概率。所以,我们不想回答“这个文本的编码是什么?”这个问题,我们试图理解“这个文本最可能的编码是什么?”。

俄罗斯流行科技博客中的一个人发明了这种方法:

在您想要支持的每种编码中构建字符代码的概率范围。您可以使用您的语言中的一些大文本来构建它(例如,一些小说,英语使用莎士比亚,俄语使用托尔斯泰,哈哈)。你会得到这样的:

    encoding_1:
    190 => 0.095249209893009,
    222 => 0.095249209893009,
    ...
    encoding_2:
    239 => 0.095249209893009,
    207 => 0.095249209893009,
    ...
    encoding_N:
    charcode => probabilty

接下来。您采用未知编码的文本,并且对于“概率字典”中的每种编码,您都在搜索未知编码文本中每个符号的频率。符号的概率总和。具有更高评级的编码可能是赢家。更大文本的更好结果。

如果您有兴趣,我很乐意帮助您完成这项任务。我们可以通过构建两个字符的概率列表来大大提高准确性。

顺便说一句。 mb_detect_encoding 确实不起作用。是的,完全没有。请查看“ext/mbstring/libmbfl/mbfl/mbfl_ident.c”中的 mb_detect_encoding 源代码。

【讨论】:

【参考方案5】:

没有办法完全准确地识别字符串的字符集。 有一些方法可以尝试猜测字符集。 mb_detect_encoding() 是其中一种方法,可能/目前是 PHP 中最好的方法。这将扫描您的字符串并查找某些字符集独有的内容。根据您的字符串,可能不会出现这种可区分的情况。

采用 ISO-8859-1 字符集与 ISO-8859-15 (http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1)

只有少数几个不同的字符,更糟糕的是,它们用相同的字节表示。没有办法检测,在不知道它的编码的情况下给定一个字符串,字节 0xA4 是否应该在您的字符串中表示 ¤ 或 €,因此无法知道它的确切字符集。

(注意:您可以添加人为因素,或者甚至更高级的扫描技术(例如 Oroboros102 建议的),以尝试根据周围的上下文找出字符应该是 ¤ 还是 €,尽管这看起来像一座太远的桥)

e.g. 之间有更明显的区别UTF-8 和 ISO-8859-1,因此当您不确定时,仍然值得尝试弄清楚,尽管您可以而且永远不应该相信它是正确的。

有趣的阅读:http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

还有其他方法可以确保正确的字符集。关于表单,尽量强制使用 UTF-8(查看雪人以确保您在每个浏览器中提交的文件都是 UTF-8:http://intertwingly.net/blog/2010/07/29/Rails-and-Snowmen) 这样做后,至少您可以确定通过您的表单提交的每个文本都是 utf_8。关于上传的文件,尝试通过例如在其上运行 unix 'file -i' 命令。 exec() (如果可能在您的服务器上)来帮助检测(使用文档的 BOM。) 关于抓取数据,您可以阅读通常指定字符集的 HTTP 标头。解析 XML 文件时,查看 XML 元数据是否包含字符集定义。

而不是试图自动猜测字符集,您应该首先尝试在可能的情况下自己确保某个字符集,或者尝试从您获取它的来源(如果适用)中获取定义,然后再进行检测。

【讨论】:

带有加密数据的表格和电子邮件注册链接。这就是我试图让我的输入为 UTF-8 或什么都没有的地方。你觉得我的回答怎么样?乐于助人的 cmets 表示赞赏。谢谢。【参考方案6】:

对我来说主要问题是我不知道任何字符串的源将是什么编码 - 它可能来自文本框(只有在用户实际提交表单时才使用),或者它可能来自上传的文本文件,所以我真的无法控制输入。

我认为这不是问题。应用程序知道输入的来源。如果它来自表单,请在您的情况下使用 UTF-8 编码。这样可行。只需验证提供的数据是否正确编码(验证)。请记住,并非所有数据库都支持全范围的 UTF-8。

如果它是一个文件,您不会将其以 UTF-8 编码保存到数据库中,而是以二进制形式保存。再次输出文件时,也使用二进制输出,这样就完全透明了。

您的想法很好,用户可以告诉编码,因为他/她在下载文件后无论如何都可以告诉,因为它是二进制的。

所以我必须承认,我没有看到您提出的问题的具体问题。但也许您可以添加更多细节,说明您的问题是什么。

【讨论】:

您会看到我的回答并提出异议吗?建设性的 cmets 表示赞赏。谢谢。【参考方案7】:

如果你愿意“把它带到控制台”,我推荐enca。与相当简单的mb_detect_encoding 不同,它使用“解析、统计分析、猜测和黑魔法的混合来确定它们的编码”(哈哈 - 参见man page)。但是,如果要检测此类特定于国家/地区的编码,通常必须传递输入文件的语言。 (但是,mb_detect_encoding 本质上具有相同的要求,因为编码必须出现在传递的编码列表中的“正确位置”才能被检测到。)

enca 也出现在这里:How to find encoding of a file in Unix via script(s)

【讨论】:

【参考方案8】:
public function convertToUtf8($text) 
    if(!$this->html)
        $this->html = cURL('http://'.$this->url, array('timeout' => 15));

    $html = $this->html;
    preg_match('/<meta.*?charset=(|\")(.*?)("|\")/i', $html, $matches);

    $charset = $matches[2];

    if($charset)
        return mb_convert_encoding($text, 'UTF-8', $charset);
    else
        return $text;

cURL 默认选项:

curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

我尝试过这样的事情。它帮助了我。如果在元字符集信息上找到,我正在转换,否则什么也不做。

【讨论】:

errr,你能检查你的函数并更正变量吗? 什么是 $url?什么是 $html?【参考方案9】:

这里有一些非常好的答案和尝试回答您的问题。我不是编码大师,但我理解您希望有一个 pure UTF-8 堆栈一直到您的数据库。我一直在为表、字段和连接使用 mysqlutf8mb4 编码。

我的情况归结为“当数据来自 HTML 表单或电子邮件注册链接时,我只希望我的清理程序、验证程序、业务逻辑和准备好的语句处理 UTF-8。”所以,以我的简单方式,我从这个想法开始:

    尝试检测编码:$encodings = ['UTF-8', 'ISO-8859-1', 'ASCII']; 如果无法检测到编码,throw new RuntimeException 如果输入是UTF-8,继续。

    否则,如果是ISO-8859-1ASCII

    一个。尝试转换为 UTF-8(等待,未完成)

    b.检测转换值的编码

    c。如果上报的编码和转换后的值都是UTF-8,则继续。

    d。否则,throw new RuntimeException

来自我的抽象类Sanitizer

    private function isUTF8($encoding, $value)
    
        return (($encoding === 'UTF-8') && (utf8_encode(utf8_decode($value)) === $value));
    

    private function utf8tify(&$value)
    
        $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];

        mb_internal_encoding('UTF-8');
        mb_substitute_character(0xfffd); //REPLACEMENT CHARACTER
        mb_detect_order($encodings);

        $stringEncoding = mb_detect_encoding($value, $encodings, true);

        if (!$stringEncoding) 
            $value = null;
            throw new \RuntimeException("Unable to identify character encoding in sanitizer.");
        

        if ($this->isUTF8($stringEncoding, $value)) 
            return;
         else 
            $value = mb_convert_encoding($value, 'UTF-8', $stringEncoding);
            $stringEncoding = mb_detect_encoding($value, $encodings, true);

            if ($this->isUTF8($stringEncoding, $value)) 
                return;
             else 
                $value = null;
                throw new \RuntimeException("Unable to convert character encoding from ISO-8859-1, or ASCII, to UTF-8 in Sanitizer.");
            
        

        return;
    

人们可以提出一个论点,即我应该将编码问题从我的抽象Sanitizer 类中分离出来,然后简单地将Encoder 对象注入Sanitizer 的具体子实例中。但是,我的方法的主要问题是,在没有更多知识的情况下,我只是拒绝了我不想要的编码类型(并且我依赖于 PHP mb_* 函数)。如果没有进一步的研究,我不知道这是否会伤害某些人群(或者,如果我丢失了重要信息)。所以,我需要了解更多。我找到了这篇文章。

What every programmer absolutely, positively needs to know about encodings and character sets to work with text

此外,当加密数据添加到我的电子邮件注册链接(使用OpenSSLmcrypt)时会发生什么?这会干扰解码吗? Windows-1252 怎么样?安全隐患如何?在Sanitizer::isUTF8 中使用utf8_decode()utf8_encode() 是可疑的。

人们已经指出了 PHP mb_* 函数的缺点。我从来没有花时间调查iconv,但如果它比 mb_*f​​unctions 更有效,请告诉我。

【讨论】:

我发现了这个,***.com/a/3521396/1429677 这个问题的优秀答案,这里是库github.com/neitanod/forceutf8【参考方案10】:

您的问题似乎得到了很好的回答,但我有一种方法可以简化您的情况:

我在尝试从 mysql 返回字符串数据时遇到了类似的问题,甚至将数据库和 php 都配置为返回格式化为 utf-8 的字符串。我得到错误的唯一方法实际上是从数据库中返回它们。

最后,通过网络航行,我找到了一个非常简单的方法来处理它:

鉴于您可以将所有这些类型的字符串数据以不同的格式和排序规则保存在 mysql 中,您只需在 php 连接文件中将排序规则设置为 utf-8,如下所示:

$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");

Wich 意味着首先您以任何格式或排序规则保存数据,然后仅在返回到您的 php 文件时将其转换。

希望对您有所帮助!

【讨论】:

【参考方案11】:

如果文本是从 mysql 数据库中检索的,您可以尝试在 DB 连接后添加它。

mysqli_set_charset($con, "utf8");

https://www.php.net/manual/en/mysqli.set-charset.php

【讨论】:

什么是“BD 连接”?你的意思是“数据库连接”吗? 是的@PeterMortensen 更改了我原来的帖子。【参考方案12】:

那里有几个库。 onnov/detect-encoding 看起来很有希望。它声称比 mb_detect_encoding

做得更好

将未知字符编码的字符串转换为UTF-8的示例用法:

use Onnov\DetectEncoding\EncodingDetector;
$detector->iconvXtoEncoding('Проверяемый текст')

简单地检测编码:

$encoding = $detector->getEncoding('Проверяемый текст');

【讨论】:

以上是关于PHP:在不知道原始字符集的情况下将任何字符串转换为 UTF-8,或者至少尝试一下的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 ToString() 的情况下将 Int 转换为 C# 中的字符串?

处理 requestAttributionDetailsWithBlock 和 NSDictionary。在不知道它到底是啥的情况下将任何类型转换为 NSString

在不了解文化的情况下将月份名称从一种语言转换为另一种语言?

在不访问库的情况下将整数转换为字符串

如何在不使用 DateTimeFormatter 的情况下将字符串转换为 LocalDate [重复]

我可以在不更改项目顺序的情况下将 JSON 字符串转换为 Flex ArrayCollection 吗?