将Ruby读取CSV文件作为UTF-8,并/或将ASCII-8Bit编码转换为UTF-8

67

我正在使用ruby 1.9.2版本。

我试图解析包含一些法语单词(例如 spécifié)的CSV文件,并将其内容放入MySQL数据库中。

当我从CSV文件中读取行时,

file_contents = CSV.read("csvfile.csv", col_sep: "$")
元素以字符串形式返回,这些字符串是ASCII-8BIT编码的(spécifié变成了sp\xE9cifi\xE9),像“spécifié”这样的字符串无法正确保存到我的MySQL数据库中。Yehuda Katz说ASCII-8BIT实际上是指“二进制”数据,这意味着CSV不知道如何读取适当的编码。因此,如果我尝试强制CSV使用以下编码:file_contents = CSV.read("csvfile.csv", col_sep: "$", encoding: "UTF-8"),则会收到以下错误消息。
ArgumentError: invalid byte sequence in UTF-8: 

如果我回到我的原始ASCII-8BIT编码的字符串并检查我的CSV读取为ASCII-8BIT的字符串,它看起来像这样“Non sp \ xE9cifi \ xE9”而不是“Non spécifié”。

我不能通过这样做将“Non sp \ xE9cifi \ xE9”转换为“Non spécifié”"Non sp\xE9cifi\xE9".encode("UTF-8"),因为我会得到这个错误:

Encoding::UndefinedConversionError:从ASCII-8BIT到UTF-8的"\xE9"

Katz指出,这将发生,因为ASCII-8BIT实际上不是一个正确的字符串“编码”。

问题:

  1. 我可以让CSV以适当的编码方式读取我的文件吗?如何做到?
  2. 如何将ASCII-8BIT字符串转换为UTF-8以便在MySQL中进行适当的存储?

听起来这个文件可能不是UTF-8编码的;你有检查过文件的实际编码吗? - coreyward
3
您的文件未以UTF-8编码。在UTF-8中,é应该是C3 A9,而不是E9。看起来您正在处理ISO-8859-1编码。 - deceze
3
我想我弄清楚了:my_ascii_8bit_string.unpack("C*").pack("U*") 似乎可行。 - user141146
@deceze:是的,文件不是UTF-8编码的,但我想用Ruby的方式来解决它。 - user141146
然后正确的方法是将CSV文件以ISO-8859-1编码读取,并使用编码转换函数将结果从ISO-8859-1转换为UTF-8。不幸的是,我的Ruby水平不足以告诉你如何做到这一点。 - deceze
3个回答

71

deceze 是正确的,那是 ISO8859-1(也称为 Latin-1)编码的文本。请尝试此操作:

file_contents = CSV.read("csvfile.csv", col_sep: "$", encoding: "ISO8859-1")

如果那不起作用,您可以使用Iconv来修复单个字符串,例如:

require 'iconv'
utf8_string = Iconv.iconv('utf-8', 'iso8859-1', latin1_string).first
如果latin1_string"Non sp\xE9cifi\xE9",那么utf8_string将是"Non spécifié"。另外,Iconv.iconv可以一次处理整个数组:
utf8_strings = Iconv.iconv('utf-8', 'iso8859-1', *latin1_strings)

使用更新的Ruby版本,您可以执行以下操作:

utf8_string = latin1_string.force_encoding('iso-8859-1').encode('utf-8')

其中latin1_string认为它是在ASCII-8BIT编码下,但实际上是在ISO-8859-1编码下。


3
请注意,Ruby现在希望您使用String#encode而不是使用iconv - duma
1
@duma:现在好了吗?我放弃了旧的Iconv东西,并添加了一个简短的说明,建议使用force_encodingencode代替Iconv。 - mu is too short
1
CSV.foreach 对我而言是有效的,但我必须使用 encoding: "iso-8859-1" 而非 encoding: "ISO8859-1" - ltrainpr

36

在 Ruby 版本大于等于 1.9 的情况下,您可以使用

file_contents = CSV.read("csvfile.csv", col_sep: "$", encoding: "ISO8859-1:utf-8")

ISO8859-1:utf-8 的意思是:这个 csv 文件采用 ISO8859-1 编码,但将内容转换为 utf-8。

如果您喜欢更详细的代码,可以使用以下代码:

file_contents = CSV.read("csvfile.csv", col_sep: "$", 
    external_encoding: "ISO8859-1", 
    internal_encoding: "utf-8"
  )

1
太棒了。以前,我必须为这个utf-16 csv输入bomCSV.read('nom_nom_nom.csv', { :headers => true, :col_sep => "\t", :encoding => 'bom|utf-16le'}),否则它会抛出错误。 现在是: external_encoding: 'utf-16', internal_encoding: "utf-8"})```。 - Hahn

1
我已经处理了一段时间的问题,但其他任何解决方案都不适用于我。
有效的方法是将有冲突的“字符串”存储在一个“二进制”文件中,然后正常读取该文件并使用此“字符串”来提供CSV模块。
tempfile = Tempfile.new("conflictive_string")
tempfile.binmode
tempfile.write(conflictive_string)
tempfile.close
cleaned_string = File.read(tempfile.path)
File.delete(tempfile.path)
csv = CSV.new(cleaned_string)

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