20

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

我不知道输入编码

示例:

> "- 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 中。

4

5 回答 5

20

我认为您无法毫无困难地全局关闭 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 并替换所有无效字节的好方法。

于 2013-06-10T16:34:23.403 回答
6

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

于 2013-06-14T19:15:45.127 回答
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。

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

于 2013-06-09T23:22:25.753 回答
2

Ruby 1.9 和 2.0 中的编码似乎有点棘手。\xFC 是 ISO-8859-1 中特殊字符 ü 的代码,但代码 FC 也出现在 ü 的 UTF-8 U+00FC = \u0252(和 UTF-16)中。它可能是 Ruby打包/解包函数的产物。使用 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ü -"
于 2013-10-02T17:25:05.340 回答
2

如果您可以配置您的数据库/页面/任何内容以提供 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
于 2013-06-10T16:10:18.227 回答