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

In Ruby, how to UTF-8 encode this weird character?

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

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

我在使用 Fixing Incorrect String Encoding From 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 中未定义的五个字节来确保不会引发错误。

一旦Ruby编码错误,字符将保持不正确,根据原来的错误。转换只是将现在错误的字符转换为新的编码。

纠正Ruby的输入错误,需要用到force_encoding的方法,这个方法不做转换,只是纠正Ruby的注释什么编码 String 有。

在您的情况下,在您从数据库中读取值之前发生了错误。如果您找出问题字节:bytes = %w(195 162 226 130 172 226 132 162).map(&:to_i) 它们看起来是 UTF-8 编码,并且 已经 在数据库中进行了双重编码。您可能会假设将这些写入数据库的任何内容都存在问题(请注意,如果它是一个实时进程,这是一个需要排序的错误,您将继续获得这些错误的值)。

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

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

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(如果您有多个不同编码的源,它可能会比这更复杂)。