在 Ruby 中,如何将这个奇怪的字符进行 UTF-8 编码?

4

我正在从外部数据库导入内容,该数据库受到各种奇怪字符的感染,例如:

> str
=> "Nature’s Variety, Best Friends Animal Society team up"

从上下文来看,’代表右单引号。在cp1252编码中:

> str.encode('cp1252')
=> "Nature\xE2\x80\x99s Variety, Best Friends Animal Society team up"

那么我该如何将它转换为正确的UTF-8字符?这是我尝试过的方法:

> str.encode('UTF-8')
=> "Nature’s Variety, Best Friends Animal Society team up"

> str.encode('cp1252').encode('UTF-8')
=> "Nature’s Variety, Best Friends Animal Society team up"

> str.encode('UTF-8', invalid: :replace, replace: '?', undef: :replace)
=> "Nature’s Variety, Best Friends Animal Society team up"

> str.encode('cp1252').encode('UTF-8', invalid: :replace, replace: '?', undef: :replace)                                                                  
=> "Nature’s Variety, Best Friends Animal Society team up"

我更希望找到一种通用的重新编码方法,这样它就能处理所有这些错误编码的字符。但如果必须这样做,我将逐个搜索并替换。但我也无法使其工作:

> str.encode('cp1252').gsub('\xE2/x80/x99', "'")
=> "Nature\xE2\x80\x99s Variety, Best Friends Animal Society team up"

> str.encode('cp1252').gsub(%r{\xE2\x80\x99}, "'")
SyntaxError: unexpected tIDENTIFIER, expecting $end

> str.encode('cp1252').gsub(Regexp.escape('\xE2\x80\x99'), "'")
=> "Nature\xE2\x80\x99s Variety, Best Friends Animal Society team up"

我想这么做,但是我甚至无法将这些字符粘贴到我的REPL中:

> str.gsub('’', "'")

当我尝试时,我收到以下错误提示:
> str.gsub('C"b,b,b
* "', ",")
=> "Nature’s Variety, Best Friends Animal Society team up"

令人沮丧。有没有关于如何正确将其编码为UTF-8的建议?
编辑:根据实际字符串字节的请求:
> str.bytes.to_a.join(' ')
=> "78 97 116 117 114 101 195 162 226 130 172 226 132 162 115 32 86 97 114 105 101 116 121 44 32 66 101 115 116 32 70 114 105 101 110 100 115 32 65 110 105 109 97 108 32 83 111 99 105 101 116 121 32 116 101 97 109 32 117 112"

1
当你遇到’乱码时,str.encoding是什么?底层的字节是什么? - mu is too short
@muistooshort,str.encoding # => #<Encoding UTF-8> - Mori
2个回答

6

我在修复MySQL中的不正确字符串编码时遇到了问题。您需要设置正确的编码,然后强制将其恢复。

fallback = {
  "\u0081" => "\x81".force_encoding("CP1252"),
  "\u008D" => "\x8D".force_encoding("CP1252"),
  "\u008F" => "\x8F".force_encoding("CP1252"),
  "\u0090" => "\x90".force_encoding("CP1252"),
  "\u009D" => "\x9D".force_encoding("CP1252")
}

str.encode('CP1252', fallback: fallback).force_encoding('UTF-8')

根据你的数据,可能不需要回退,但是为了处理CP1252中未定义的五个字节,它确保不会引发错误。


1
这很荒谬,这竟然是解决这个问题的方法,但它非常有效,所以谢谢! - Jared

3

一旦 Ruby 错误处理字符编码,字符就会一直保持错误,直到原始错误得到更正。转换只会将现在错误的字符转换为新的编码。

要纠正 Ruby 输入时的错误,您需要使用 force_encoding 方法,它不会进行转换,只是更正 Ruby 关于 String 编码的记录。

在您的情况下,故障发生在从数据库中读取值之前。如果您挑选出问题字节:bytes = %w(195 162 226 130 172 226 132 162).map(&:to_i),它们看起来是以 UTF-8 编码,并且已经在数据库中双重编码。您可以假设出现了问题,并且需要解决这个写入错误值到数据库的 bug(请注意,如果这是一个正在运行的进程,则需要对其进行排序,否则您将继续获得这些错误值)。

所发生的是,您的数据库(或写入它的代码)收到了表示正确字符的一些 UTF-8 字节,但是假定它们是 CP1252,然后将其转换为 UTF-8。它进行了该转换并将有效的 UTF-8(但错误的字符)写入了数据库中。

如果我在 Ruby 控制台中使用 UTF-8 编码作为默认编码,模拟您的问题,我可以执行以下操作:

str = "Nature’s Variety, Best Friends Animal Society team up"
 => "Nature’s Variety, Best Friends Animal Society team up"
str = str.force_encoding('CP1252').encode('UTF-8')
 => "Nature’s Variety, Best Friends Animal Society team up"

故障是可逆的,如下所示:
str = str.encode('CP1252').force_encoding('UTF-8')
 => "Nature’s Variety, Best Friends Animal Society team up"

encode('CP1252')是为了纠正最初的错误转换。

force_encoding('UTF-8')将编码设置回系统最可能最初接收到的编码。

您需要找出在系统中假定输入为CP1252的地方,并改为假定为UTF-8(如果有多个来源使用不同的编码,则可能会更加复杂)。


1
这对我失败了。那行代码出现了“_ArgumentError: wrong number of arguments(1 for 0)_”错误。将 encoding 改为 encode 会得到一个带有其他奇怪字符的字符串:“ ”Nature’s Variety, Best Friends Animal Society team up”。 - Mori
抱歉,我把第二个方法的名称搞错了,已经更正。你原来的字符串不是CP1252编码。在任何调用“encode”之前,你能提供原始字节吗? - Neil Slater
str.bytes 返回一个枚举器。我该如何从中获取实际的字节? - Mori
旧版的 Ruby。str.bytes.to_a - Neil Slater
我已经将字节添加到问题中。 - Mori
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接