【问题标题】:Is testing for UTF-8 strings in PHP a reliable method?在 PHP 中测试 UTF-8 字符串是一种可靠的方法吗?
【发布时间】:2012-03-31 11:41:23
【问题描述】:

我在另一个答案中找到了一个有用的功能,我想知道是否有人可以向我解释它在做什么以及它是否可靠。我使用的是 mb_detect_encoding(),但在 Linux 操作系统上读取 ISO 8859-1 文件时它不正确。

这个功能似乎在我测试的所有情况下都有效。

这里是问题:Get file encoding

函数如下:

function isUTF8($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);
}

这是检测 UTF-8 字符串的可靠方法吗? 它到底在做什么?它可以变得更健壮吗?

【问题讨论】:

  • 为什么不使用 mb_detect_encoding (php.net/manual/en/function.mb-detect-encoding.php) 之类的东西?
  • 只想提一下,这个函数认为“1”字符串不是utf8,而它是(要明确它只是一个ascii,但它仍然应该并入utf8)跨度>
  • @summea 你读过这个问题了吗?
  • @GaryWilloughby 读过这个问题,但我不记得你之前使用过mb_detect_encoding;对于那个很抱歉。我仍然认为在这里使用mb_detect_encoding 是值得的,尽管......即使它被包裹在其他东西中。看看 Greg Tisza 的评论,如果你有机会,关于使用“严格模式”(php.net/manual/en/function.mb-detect-encoding.php#102510)
  • 应该注意,问题中发布的函数实际上并未检测任意字符串是否为有效的 UTF-8。它只检测字符串是否包含“UTF-8 范围内的非 ascii 多字节序列”。因此,像“hello world”这样的普通 ascii 字符串将无法通过测试。有关该功能来自何处的更详细说明,请参阅下面的答案。

标签: php string utf-8


【解决方案1】:

如果您不知道字符串的编码,则不可能以任何准确度猜测编码。这就是为什么mb_detect_encoding 根本不起作用。但是,如果您知道字符串应该采用什么编码,则可以使用mb_check_encoding 检查它是否是该编码中的有效字符串。它或多或少地完成了您的正则表达式所做的事情,可能更全面一些。它可以用明确的“是”或“否”来回答问题“这个字节序列在 UTF-8 中有效吗?”。这并不一定意味着字符串实际上是 以该编码编码的,只是它可能是。例如,不可能将使用全部 8 位的任何单字节编码与使用 8 位的任何其他单字节编码区分开来。但是 UTF-8 应该是相当可区分的,尽管您可以生成,例如,也恰好是有效 UTF-8 字节序列的 Latin-1 编码字符串。

简而言之,没有办法确定。如果您期望 UTF-8,请检查您收到的字节序列在 UTF-8 中是否有效,然后您可以安全地将字符串视为 UTF-8。除此之外,您几乎无能为力。

【讨论】:

  • 我可以测试该字符串是否为有效的 UTF-8 字符串,但“Hello World”即使是 ASCII 格式也将通过该测试,但您能否以一种可以告诉您的方式进行验证如果它是有效的 latin-1 或 ascii,而不是 UTF-8,你呢?
  • "Hello World" 是有效的 ASCII and Latin-1 and UTF-8!
  • 我通过测试它是否是 UTF-8 来绕过它,如果是,然后它无法测试有效的 ASCII,我认为它是 ASCII,否则它是 UTF-8。我需要这个,因为如果它是 UTF-8,我需要在 Zip 文件头结构中设置一个标志,但如果不是,则不应这样做。
  • “Hello World”在几乎所有编码中都有效!!如果mb_check_encoding 说它在某种编码中有效,那么它就是!它们都同样有效!任君挑选!对 ASCII 进行测试,如果它失败了,你就认为它是 ASCII 仍然没有任何意义。
【解决方案2】:

好吧,它只检查字符串是否包含恰好对应于有效 UTF-8 代码点的字节序列。但是,它不会标记序列 0x00-0x7F,它是 UTF-8 的 ASCII 兼容子集。

编辑:顺便说一句,我猜想mb_detect_encoding()“无法正常工作”的原因是因为您的 Latin-1 编码文件仅使用了 ASCII 兼容子集,这在 UTF-8 中也有效。难怪mb_detect_encoding() 会将其标记为 UTF-8 并且它是“正确的”,如果数据只是纯 ASCII,那么答案 UTF-8 与 Latin-1、ASCII 或任何其他的一样好扩展的 ASCII 编码。

【讨论】:

  • 问题是我需要做进一步的编码,所以我需要在再次编码之前确切地知道它是什么。是的,这是扩展 ASCII 集的问题。
【解决方案3】:

这只会检测字符串的一部分是否是正式有效的 UTF-8 序列,忽略一个代码单元编码字符(表示 ASCII 中的代码点)。要使该函数返回 true,只要有一个字符看起来像非 ASCII UTF-8 编码字符就足够了。

【讨论】:

    【解决方案4】:

    基本上没有。

    • 任何 UTF8 字符串是有效的 8 位编码字符串(即使它会产生乱码)。
    • 另一方面,大多数带有扩展 (128+) 个字符的 8 位编码字符串是不是有效的 UTF8,但与任何其他随机字节序列一样,它们可能碰巧是。
    • 当然,任何 ASCII 文本都是有效的 UTF8,所以 mb_detect_encoding 实际上这样说是正确的。不,使用 ASCII 文本作为 UTF8 不会有任何问题。这就是 UTF8 首先起作用的原因。

    据我了解,您提供的函数不会检查字符串的 有效性,只是它包含一些恰好与 UTF8 相似的序列,因此该函数可能会出错更糟糕。您可能希望在严格模式下同时使用此函数 mb_detect_encoding,并希望它们相互抵消误报。

    如果文本是用非拉丁字母书写的,检测多字节编码的“智能”方法是查找以相同位开头的大小相等的字节块序列。例如,俄语单词“привет”如下所示:

    11010000 10111111
    11010001 10000000
    11010000 10111000
    11010000 10110010
    11010000 10110101
    11010001 10000010
    

    但是,这不适用于拉丁字母(可能还有中文)。

    【讨论】:

      【解决方案5】:

      有问题的函数(用户 pilif 在链接问题中发布的函数)似乎取自 PHP 手册中 mb_detect_encoding() 页面上的 this comment

      正如作者所说,该函数仅用于“检查字符串是否包含 UTF-8 字符”,它仅查找“UTF-8 范围内的非 ascii 多字节序列” .因此,如果您的字符串仅包含简单的 ascii 字符(如英文文本),该函数将返回 false(实际上为零),这可能不是您想要的。

      他的函数基于同一页面上 this previous comment 中的另一个函数,实际上,该函数旨在检查字符串是否为 UTF-8,并且基于 W3C 某人创建的 this regular expression

      这是原始的、正常工作(我已经测试过)的函数,它会告诉您字符串是否为 UTF-8:

      // Returns true if $string is valid UTF-8 and false otherwise.
      function is_utf8($string) {
      
          // From http://w3.org/International/questions/qa-forms-utf-8.html
          return 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);
      
      } // function is_utf8
      

      【讨论】:

      • 顺便说一句,使用mb_detect_encoding() 的一个问题是它不支持“Mac OS Roman”(或“macintosh”)字符集,这在 OS X 上仍然有些常用。它会错误地将其识别为 UTF-8。
      【解决方案6】:

      这可能不是您问题的答案(也许是,请参阅下面的更新),但它可能是您问题的答案。查看我的 Encoding 类,它具有将字符串转换为 UTF8 的方法,无论它们是否已经以 Latin1、Win1252 或 UTF8 编码,或者它们的混合:

      Encoding::toUTF8($text_or_array);
      Encoding::toWin1252($text_or_array);
      Encoding::toISO8859($text_or_array);
      
      // fixes UTF8 strings converted to UTF8 repeatedly: 
      //  "FÃÂédÃÂération" to "Fédération"
      Encoding::fixUTF8($text_or_array);  
      

      https://stackoverflow.com/a/3479832/290221

      该函数逐字节运行,并确定它们中的每一个是否需要转换。

      更新:

      再想一想,这实际上可能是您问题的答案:

      require_once('Encoding.php');
      
      function validUTF8($string){
          return Encoding::toUTF8($string) == $string;
      }
      

      这里是编码类: https://github.com/neitanod/forceutf8

      【讨论】:

      • 这只能在最佳猜测的基础上进行。如果我打算写“FédÃÂération”怎么办?就像在此页面上一样,以演示编码问题。如果您由于编码错误处理而搞砸了文本,则需要修复这些错误处理,而不是文本。
      • 没错。这就是为什么最后一个功能从 toUTF8() 分离到它自己的功能中的原因。我用命令行程序做了 fixUTF8() 来修复一些文件。它不适用于实时网站。尽管如此,我确实在实时网站上使用 toUTF8()。
      • 我也不太明白需要单独检查每个字节。您期望混合编码的字符串吗?如果是这样,您的问题在其他地方。编码具有您真正需要知道您正在处理的内容的性质。您不能将字符串视为黑盒,您只能将一种编码转换为另一种编码。如果不知道这个 from 是什么,您就无法可靠地获得您正在寻找的结果。虽然您的课程是一项相当多的工作,但我真的建议您不要使用它。
      • 很抱歉试图把这一点带回家,但你这样做是必不可少的,对吧?您尝试确认字符串在 UTF-8 中是否有效,如果不是,您假设它是 Latin-1 或可能是 Windows-1252。您可以使用if (!mb_check_encoding($str, 'UTF-8')) $str = iconv('ISO-8859-1', 'UTF-8', $str) 获得大致相同的效果。添加preg_match 检查典型的 Windows-1252 字节序列以尝试区分 ISO-8859-1 和 Windows-1252,这永远不会 100% 准确。与其做所有这些,不如了解自己的编码。
      • 首先,正如您所猜测的,我正在处理混合编码。我同意你关于了解你的输入的重要性,但遗憾的是,在某些情况下,你永远无法确定。我见过文件通知编码并使用另一个。
      猜你喜欢
      • 2010-10-08
      • 2017-06-18
      • 2021-10-11
      • 2012-02-06
      • 2018-09-07
      • 2016-05-31
      • 1970-01-01
      • 2011-08-14
      • 1970-01-01
      相关资源
      最近更新 更多