在Ruby中,如何使用UTF-8编码这个奇怪的角色?

时间:2015-03-24 17:37:03

标签: ruby encoding utf-8

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

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

2 个答案:

答案 0 :(得分:5)

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中未定义的五个字节来引发错误。

答案 1 :(得分:2)

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

要纠正Ruby在输入上的错误,您需要使用force_encoding方法,该方法不进行转换,它只是纠正了Ruby对String编码的注释。

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

发生了什么事情是你的DB(或写入它的代码)收到了一些代表正确字符的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(如果您有不同编码的多个源,则可能会比这更复杂。)