【问题标题】:Ensuring valid UTF-8 in PHP确保 PHP 中有效的 UTF-8
【发布时间】:2010-12-04 03:40:54
【问题描述】:

我正在使用 PHP 来处理来自各种来源的文本。我预计它不会是 UTF-8、ISO 8859-1Windows-1252 以外的任何东西。如果不是其中之一,我只需要确保文本转换为有效的 UTF-8 字符串,即使字符丢失。 iconv 的 //TRANSLIT 选项能解决这个问题吗?

例如,此代码是否可以确保将字符串安全地插入到 UTF-8 编码文档(或数据库)中?

function make_safe_for_utf8_use($string) {

    $encoding = mb_detect_encoding($string, "UTF-8,ISO-8859-1,WINDOWS-1252");

    if ($encoding != 'UTF-8') {
        return iconv($encoding, 'UTF-8//TRANSLIT', $string);
    }
    else {
        return $string;
    }
}

【问题讨论】:

    标签: php encoding utf-8


    【解决方案1】:

    UTF-8 可以存储任何 Unicode 字符。如果您的编码是其他任何东西,包括 ISO-8859-1 或 Windows-1252,UTF-8 可以存储其中的每个字符。因此,当您将字符串从任何其他编码转换为 UTF-8 时,您不必担心丢失任何字符。

    此外,ISO-8859-1 和 Windows-1252 都是单字节编码,其中任何字节都有效。在技​​术上无法区分它们。我会选择 Windows-1252 作为非 UTF-8 序列的默认匹配,因为唯一不同解码的字节是 0x80-0x9F 范围。这些在 Windows-1252 中解码为各种字符,如智能引号和欧元,而在 ISO-8859-1 中,它们是几乎从未使用过的不可见控制字符。 Web 浏览器有时可能会说它们使用的是 ISO-8859-1,但实际上它们通常会使用 Windows-1252。

    此代码能否确保将字符串安全插入到 UTF-8 编码文档中

    为此,您当然希望将可选的“strict”参数设置为 TRUE。但我不确定这是否真的涵盖了所有无效的 UTF-8 序列。该函数不声称明确检查字节序列的 UTF-8 有效性。已知 mb_detect_encoding 之前会错误地猜测 UTF-8 的情况,但我不知道在严格模式下是否仍然会发生这种情况。

    如果您想确定,请自己使用W3-recommended regex

    if (preg_match('%^(?:
          [\x09\x0A\x0D\x20-\x7E]            # ASCII
        | [\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))
        return $string;
    else
        return iconv('CP1252', 'UTF-8', $string);
    

    【讨论】:

    • 非常感谢。我知道开发人员总是对正则表达式的缓慢发表评论——在包含大量文本的大循环中使用它时应该多小心?例如,一个循环迭代 200 次并在每次迭代中清除 10,000 个字符的文本。
    • 虽然我不是正则表达式的粉丝,但在这种情况下它应该不会那么糟糕。当您有连续或嵌套的?/*/+ 序列时,正则表达式会变慢,这可能导致它不得不回溯寻找不同的匹配方式。在这种情况下不会发生这种情况。
    • 优秀。因此,当如上所述使用 iconv 时,如果我将 CP1252 指定为输入字符集,并且该字符串不是 CP1252 或 ISO-8859-1,它将返回一个 UTF-8 安全字符串,尽管某些字符可能会丢失。对吗?
    • 它将返回一个 UTF-8 安全字符串,是的。非 ASCII 字符将作为错误字符出现,但不是危险字符。
    • 其实这个正则表达式是错误的。它将无法匹配有效的 UTF-8 代码点(例如 chr(0))。可打印字符很好,但不是通用的 UTF-8...
    【解决方案2】:

    使用 mbstring 库,您拥有 mb_check_encoding()

    使用示例:

    mb_check_encoding($string, 'UTF-8');
    

    在最近的 Windows 10 系统上使用 PHP 7.1.9,regex 解决方案在任何字符串长度(仍为 20,000 次迭代)上都优于 mb_check_encoding()

    • 10 个字符:正则表达式 => 4 毫秒,mb_check_encoding() => 64 毫秒
    • 10000 个字符:正则表达式 => 125 毫秒,mb_check_encoding() => 2.4 秒

    【讨论】:

    • 你的系统一定是非常快,因为在一个相当现代的系统上,我在 7500 次迭代中得到了大约 5 秒的时间(尽管我正在处理一些相当大的字符串,想想一个相当现代的网站的 HTML)。
    【解决方案3】:

    请注意:您可以简单地使用 'u' 修饰符来测试字符串的 UTF-8 有效性,而不是使用经常推荐的(相当复杂的)regular expression by W3C

    <?php
      if (preg_match("//u", $string)) {
          // $string is valid UTF-8
      }
    

    【讨论】:

    【解决方案4】:

    查看http://www.phpwact.org/php/i18n/charsets 以获取有关字符集的指南。此页面链接到专门用于 UTF-8 的页面。

    【讨论】:

    • 链接好像坏了。
    【解决方案5】:

    对“iconv 是幂等的”的回答:

    iconv 也不是 - iconv 不是幂等的。

    utf8_encode()iconv() 之间的一个很大区别是 iconv 可能会引发类似“在输入字符串中检测到不完整的多字节字符”这样的错误,即使是:

    iconv('ISO-8859-1', 'UTF-8'.'//IGNORE', $str)

    在上面的代码中:

    $encoding = mb_detect_encoding($string, "UTF-8,ISO-8859-1,WINDOWS-1252");

    你必须知道mb_detect_encoding。即使是无效的 UTF-8 字符串(格式错误的 UTF-8),它也可以回答关于 uft-8 的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-10
      • 2011-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-21
      • 1970-01-01
      • 2013-02-17
      相关资源
      最近更新 更多