Ruby字符串编码从ISO-8859-1转换为UTF-8无效。

14

我正在尝试将一个字符串从ISO-8859-1编码转换为UTF-8,但似乎无法使其正常工作。以下是我在irb中所做的示例。

irb(main):050:0> string = 'Norrlandsvägen'
=> "Norrlandsvägen"
irb(main):051:0> string.force_encoding('iso-8859-1')
=> "Norrlandsv\xC3\xA4gen"
irb(main):052:0> string = string.encode('utf-8')
=> "Norrlandsvägen" 

我不确定为什么iso-8859-1格式中的Norrlandsvägen会被转换成utf-8格式中的Norrlandsvägen

我尝试了encode、encode!、encode(destinationEncoding, originalEncoding)、iconv、force_encoding以及各种奇怪的方法,但似乎都没有用。请问有人能帮我或指点一下方向吗?

Ruby新手仍然疯狂地抓头,但感谢这里所有人的回复……:)

问题背景:我正在编写一个gem,将从某些网站下载xml文件(其编码为iso-8859-1),并将其保存在存储中,我想先将其转换为utf-8格式。但像Norrlandsvägen这样的单词总是让我犯难。真的非常感谢任何帮助!

[更新]: 我意识到在irb控制台中运行此类测试可能会给我带来不同的行为,所以这是我在实际代码中拥有的内容:

def convert_encoding(string, originalEncoding) 
  puts "#{string.encoding}" # ASCII-8BIT
  string.encode(originalEncoding)
  puts "#{string.encoding}" # still ASCII-8BIT
  string.encode!('utf-8')
end

但是最后一行会出现以下错误:
Encoding::UndefinedConversionError - "\xC3" from ASCII-8BIT to UTF-8

感谢@Amadan在下面的回答中提供的帮助,我注意到如果你运行以下命令,\xC3实际上会出现在irb中:
irb(main):001:0> string = 'ä'
=> "ä"
irb(main):002:0> string.force_encoding('iso-8859-1')
=> "\xC3\xA4"

我也尝试将新变量分配给string.encode(originalEncoding)的结果,但是得到了一个更奇怪的错误:

newString = string.encode(originalEncoding)
puts "#{newString.encoding}" # can't even get to this line...
newString.encode!('utf-8')

错误信息为Encoding::UndefinedConversionError - "\xC3" to UTF-8 in conversion from ASCII-8BIT to UTF-8 to ISO-8859-1

在所有这些编码混乱中,我仍然感到很困惑,但我非常感谢大家给我的回复和帮助!非常感谢! :)


从这件事情中我学到的是:基本上不要相信任何东西哈哈(你的浏览器,你的文本编辑器,你的代码,irb,xml中的头部信息,你的控制台等)。所有这些都可能出现问题并掩盖编码问题,因此在进行调试时,请逐个检查每个可能出现问题的点。祝你调试愉快! :) - charint
3个回答

20

您分配了一个UTF-8字符串,其中包含ä。UTF-8使用两个字节来表示ä

string = 'ä'
string.encoding
# => #<Encoding:UTF-8>
string.length
# 1
string.bytes
# [195, 164]

然后你强制将字节解释为 ISO-8859-1 的形式,而不会实际改变底层表示。这不再包含 ä 了,而是包含两个字符:ä

string.force_encoding('iso-8859-1')
# => "\xC3\xA4"
string.length
# 2
string.bytes
# [195, 164]

然后您将其转换为 UTF-8。由于这不是重新解释而是翻译,因此保留这两个字符,但现在使用 UTF-8 编码:

string = string.encode('utf-8')
# => "ä" 
string.length
# 2
string.bytes
# [195, 131, 194, 164]

你没有意识到的是,最初你没有一个 ISO-8859-1 字符串,就像你从 Web-service 中获得的一样 - 你有乱码。幸运的是,这些都在你的控制台测试中;如果你使用正确的输入编码读取网站的响应,一切都应该正常工作。

对于你的控制台测试,让我们演示一下,如果你从一个正确的 ISO-8859-1 字符串开始,一切都正常:

string = 'Norrlandsvägen'.encode('iso-8859-1')
# => "Norrlandsv\xE4gen"
string = string.encode('utf-8')
# => "Norrlandsvägen"

编辑 针对您的具体问题,这应该可以解决:

require 'net/https'
uri = URI.parse("https://rusta.easycruit.com/intranet/careerbuilder_se/export/xml/full")
options = {
  :use_ssl => uri.scheme == 'https', 
  :verify_mode => OpenSSL::SSL::VERIFY_NONE
}
response = Net::HTTP.start(uri.host, uri.port, options) do |https|
  https.request(Net::HTTP::Get.new(uri.path))
end
body = response.body.force_encoding('ISO-8859-1').encode('UTF-8')

哦,谢谢!你说的很有道理,但不知怎么的,当我从我的 Web 服务读取数据时,Ruby 实际上认为它是 ASCII-8BIT 而不是 ISO-8859-1。 - charint
这是一个关于编程的内容,需要将其翻译成中文。以下是翻译后的文本:这是我尝试获取的 XML 示例:链接。我的代码如下:def convert_encoding(string, originalEncoding) string.encode(originalEncoding) string.encode!('utf-8') end其中有一些 puts 来显示字符串的编码和内容,但是我收到了 UndefinedConversionError "\xC3" from ASCII-8BIT to UTF-8 的错误消息。我是否漏掉了一些明显的东西? - charint
你是如何从 Web 服务中读取 XML 的?顺便说一下,你链接的文件是 UTF-8 编码,而不是它自己声称的 ISO-8859-1。因此,在文件中,你实际上有两个字节的 UTF-8 表示,第一个字节是 \xC3;而 ASCII-8BIT -> UTF-8 转换会在其上出现问题。讽刺的是,你甚至不需要转换 :) 只需正确地将流打开为 UTF-8,或在读取后强制将字符串转换为 UTF-8 即可。 - Amadan
我正在使用response = Net::HTTP.get_response(uri)response.body获取xml。有趣的是,如果跳过编码转换步骤,将文件原始保存在我的存储(AWS S3)中并手动下载文件,我仍然看到“Norrlandsvägen”而不是“Norrlandsvägen”。请问如何识别文件的实际编码(不考虑它的声明)?这个要点是我一直在处理的一个简化示例; 最终我想获取像[这个文件](https://rusta.easycruit.com/intranet/careerbuilder_se/export/xml/full)这样的文件。 - charint
谢谢你到目前为止提供的所有帮助!我还没有解决方案,但是你的回答帮助我澄清了我的困惑,我认为我离解决方案更近了。 :) - charint
显示剩余3条评论

2
上面的回答很到位。特别是这里的一点:
引用: force_encoding和encode有区别。前者设置字符串的编码,而后者实际上将字符串内容转码为新编码。
在我的情况下,我有一个使用iso-8859-1编码的文本文件。默认情况下,Ruby使用UTF-8编码,因此如果您尝试在不指定编码的情况下读取该文件,则会出现错误:
results = File.read(file)
results.encoding
 => #<Encoding:UTF-8> 
 results.split("\r\n")
ArgumentError: invalid byte sequence in UTF-8

因为不同编码的字符由不同字节长度表示,所以你会收到无效字节序列错误。因此,需要在File API中指定编码方式,就像强制编码一样。
results = File.read(file, encoding: "iso-8859-1")

所以一切都好了,对吗?不,如果您想开始使用UTF-8字符编码解析iso-8859-1字符串,则不行:

results = File.read(file, encoding: "iso-8859-1")
results.each do |line|
  puts line.split('¬')
end
Encoding::CompatibilityError: incompatible character encodings: ISO-8859-1 and UTF-8

为什么会出现这个错误?因为“¬”是以UTF-8格式表示的。您正在使用UTF-8字符序列对ISO-8859-1字符串进行操作。它们是不兼容的编码。因此,在将文件读取为ISO-8859-1后,您可以要求Ruby将该ISO-8859-1编码为UTF-8。这样,您将使用UTF-8字符串,并且不会出现任何问题:

results = File.read(file, encoding: "iso-8859-1").encode('UTF-8')
results.encoding
results = results.split("\r\n")
results.each do |line|
  puts line.split('¬')
end

最终,对于一些Ruby API,你不需要使用force_encoding('ISO-8859-1')。相反,你只需向API指定所期望的编码即可。但是,如果你计划使用UTF-8字符串解析它,你必须将其转换回UTF-8。


2
“force_encoding”和“encode”之间有区别。前者设置字符串的编码,而后者实际上将字符串的内容转换为新的编码。因此,下面的代码导致了你的问题:
string = "Norrlandsvägen"
string.force_encoding('iso-8859-1')
puts string.encode('utf-8') # Norrlandsvägen

以下代码将正确编码您的内容:
string = "Norrlandsvägen".encode('iso-8859-1')
string.encode!('utf-8')

这是一个在irb中运行的示例:
irb(main):023:0> string = "Norrlandsvägen".encode('iso-8859-1')
=> "Norrlandsv\xE4gen"
irb(main):024:0> string.encoding
=> #<Encoding:ISO-8859-1>
irb(main):025:0> string.encode!('utf-8')
=> "Norrlandsvägen"
irb(main):026:0> string.encoding
=> #<Encoding:UTF-8>

非常感谢您的回复!它在irb中可以运行,但是当我尝试在我的gem中运行时,出现了“UndefinedConversionError "\xC3" from ASCII-8BIT to UTF-8”的错误。似乎Ruby实际上认为来自Web服务的输入字符串是ASCII-8BIT而不是ISO-8859-1(即使xml的开头声明为“<?xml version="1.0" encoding="ISO-8859-1"?>”)。您能否给我另一个提示,告诉我可能遗漏了什么?非常感谢!:) - charint

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