【问题标题】:How can I globally ignore invalid byte sequences in UTF-8 strings?如何全局忽略 UTF-8 字符串中的无效字节序列?
【发布时间】:2013-06-03 23:10:40
【问题描述】:

我有一个 Rails 应用程序从 Rails 版本 1 的迁移中幸存下来,我想忽略它上面的 所有 无效字节序列,以保持向后兼容性。

我不知道输入编码

示例:

> "- Men\xFC -".split("n")
ArgumentError: invalid byte sequence in UTF-8
    from (irb):4:in `split'
    from (irb):4
    from /home/fotanus/.rvm/rubies/ruby-2.0.0-rc2/bin/irb:16:in `<main>'

我可以用一行代码解决这个问题,例如:

> "- Men\xFC -".unpack("C*").pack("U*").split("n")
 => ["- Me", "ü -"] 

但是,我想始终忽略无效的字节序列并禁用此错误。在 Ruby 本身或 Rails 中。

【问题讨论】:

  • 显示一些无效数据的样本。您的数据库或表中的编码是什么? Rails 需要与之匹配。 Data Rails 接收到的数据需要强制转换为数据库将存储的相同编码,否则您必须使用二进制 ASCII 或二进制 UTF-8 编码。
  • @fotanus:它适用于 ruby​​ 1.8,因为 ruby​​ 1.8 没有以相同的方式处理编码(事实上,根本没有)。参见例如yokolet.blogspot.com/2009/07/…yehudakatz.com/2010/05/05/…
  • @Denis 谢谢,我知道它发生了变化,所以这就是我要解决这个问题的原因。
  • @theTinMan 添加示例
  • 您可以尝试遍历所有字符串并将它们更改为有效的内容。另一个版本是重新打开::String 类并操作所有方法。顺便说一句,这看起来像是您的系统默认使用的标准 8 位编码。

标签: ruby-on-rails ruby encoding


【解决方案1】:

我认为您不能毫无困难地全局关闭 UTF-8 检查。相反,我会专注于修复进入应用程序的所有字符串,在它们进入的边界处(例如,当您查询数据库或接收 HTTP 请求时)。

假设传入的字符串具有 BINARY(也称为 ASCII-8BIT 编码)。可以这样模拟:

s = "Men\xFC".force_encoding('BINARY')  # => "Men\xFC"

然后我们可以使用 String#encode 将它们转换为 UTF-8,并用 UTF-8 替换字符替换任何未定义的字符:

s = s.encode("UTF-8", invalid: :replace, undef: :replace)  # => "Men\uFFFD"
s.valid_encoding?  # => true

不幸的是,上述步骤最终会破坏大量 UTF-8 代码点,因为其中的字节无法识别。如果您有一个三字节的 UTF-8 字符,例如“\uFFFD”,它将被解释为三个单独的字节,每个字节都将转换为替换字符。也许你可以这样做:

def to_utf8(str)
  str = str.force_encoding("UTF-8")
  return str if str.valid_encoding?
  str = str.force_encoding("BINARY")
  str.encode("UTF-8", invalid: :replace, undef: :replace)
end

这是我能想到的最好的了。不幸的是,我不知道告诉 Ruby 将字符串视为 UTF-8 并替换所有无效字节的好方法。

【讨论】:

  • 感谢您的回答,这是最接近全球解决方案。您总是可以重新定义字符串方法来执行此操作,但我认为我将不得不放弃并逐个处理所有代码,因为将其添加到字符串会非常不自然。
【解决方案2】:

在 ruby​​ 2.0 中,您可以使用 String#b 方法,它是 String#force_encoding("BINARY") 的短别名

【讨论】:

  • 这是非常有用的信息,我对这些信息很满意,但也许它更适合作为评论?
【解决方案3】:

如果您只想对原始字节进行操作,可以尝试将其编码为 ASCII-8BIT/BINARY。

str.force_encoding("BINARY").split("n")

不过,这不会让你的 ü 回来,因为在这种情况下你的源字符串是 ISO-8859-1(或类似的东西):

"- Men\xFC -".force_encoding("ISO-8859-1").encode("UTF-8")
 => "- Menü -"

如果您想获取多字节字符,您必须知道源字符集是什么。 一旦您将force_encoding 转换为 BINARY,您实际上将只拥有原始字节,因此不会相应地解释多字节字符。

如果数据来自您的数据库,您可以更改连接机制以使用 ASCII-8BIT 或 BINARY 编码; Ruby 应该 相应地标记它们。或者,您可以对数据库驱动程序进行猴子补丁以强制对从其中读取的所有字符串进行编码。不过,这是一把巨大的锤子,而且可能是绝对错误的做法。

正确的答案是修复你的字符串编码。这可能需要数据库修复、数据库驱动程序连接编码修复或它们的某种组合。所有字节仍然存在,但是如果您正在处理给定的字符集,您应该尽可能让 Ruby 知道您希望您的数据采用该编码。一个常见的错误是使用 mysql2 驱动程序连接到具有 latin1 编码数据的 MySQL 数据库,但为连接指定 utf-8 字符集。这会导致 Rails 从数据库中获取 latin1 数据并将其解释为 utf-8,而不是将其解释为 latin1,然后您可以将其转换为 UTF-8。

如果您可以详细说明字符串的来源,则可能会有更完整的答案。您还可以查看this answer,了解默认字符串编码的可能全局(-ish)Rails 解决方案。

【讨论】:

    【解决方案4】:

    如果您可以配置您的数据库/页面/任何内容以提供 ASCII-8BIT 字符串,这将为您提供真正的编码。

    使用 Ruby 的 stdlib 编码猜测库。通过这样的方式传递所有字符串:

    require 'nkf'
    str = "- Men\xFC -"
    str.force_encoding(NKF.guess(str))
    

    NKF 库会猜测编码(通常是成功的),并强制对字符串进行编码。如果您不想完全信任 NKF 库,也可以围绕字符串操作构建此保护措施:

    begin
      str.split
    rescue ArgumentError
      str.force_encoding('BINARY')
      retry
    end
    

    如果 NKF 没有正确猜测,这将回退到 BINARY。你可以把它变成一个方法包装器:

    def str_op(s)
      begin
        yield s
      rescue ArgumentError
        s.force_encoding('BINARY')
        retry
      end
    end
    

    【讨论】:

      【解决方案5】:

      Ruby 1.9 和 2.0 中的编码似乎有点棘手。 \xFC 是 ISO-8859-1 中特殊字符 ü 的代码,但代码 FC 也出现在 ü U+00FC = \u0252(和 UTF-16)的 UTF-8 中。它可能是 Ruby pack/unpack functions 的产物。使用 Unicode 的 U* 模板字符串打包和解包 Unicode 字符没有问题:

      >> "- Menü -".unpack('U*').pack("U*")
      => "- Menü -"
      

      如果您先解压缩 Unicode UTF-8 字符 (U),然后再打包无符号字符 (C),则可以创建“错误”字符串,即编码无效的字符串:

      >> "- Menü -".unpack('U*').pack("C*")
      => "- Men\xFC -"
      

      此字符串不再具有有效的编码。显然,可以通过应用相反的顺序来反转转换过程(有点像量子物理学中的运算符):

      >> "- Menü -".unpack('U*').pack("C*").unpack("C*").pack("U*")
      => "- Menü -"
      

      在这种情况下,也可以通过首先将其转换为 ISO-8859-1,然后再转换为 UTF-8 来“修复”损坏的字符串,但我不确定这是否会意外工作,因为代码包含在这个字符集

      >> "- Men\xFC -".force_encoding("ISO-8859-1").encode("UTF-8")
      => "- Menü -"
      >> "- Men\xFC -".encode("UTF-8", 'ISO-8859-1')
      => "- Menü -"
      

      【讨论】:

      • 有趣的帖子,但并没有真正回答问题 - 它不是一个全局解决方案,它仅适用于一个字符串。
      • 是的,可能。我遇到了类似的问题,您的带有 \xFC 字符的无效字符串是从哪里来的?我有一个 UTF-8 编码的文本文件,其中包含 ä、ö、ü 等特殊字符,并且 File.open 以某种方式返回了无效字符串,尽管编码被正确识别为 UTF-8 :-(
      • 我的字符串来自最糟糕的地方:用户上传的文件。
      猜你喜欢
      • 2019-09-17
      • 2011-02-24
      • 2015-06-15
      • 2011-01-26
      • 2015-07-04
      • 2012-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多