我正在尝试使用mongoid创建数据库,但是Mongo正在创建数据库,我在utf8中编码数据库时遇到了问题
extract_data类:
class ExtractData
include Mongoid::Document
include Mongoid::Timestamps
def self.create_all_databases
@cbsa2msa = DbForCsv.import!('./share/private/csv/cbsa_to_msa.csv')
@zip2cbsa = DbForCsv.import!('./share/private/csv/zip_to_cbsa.csv')
end
def self.show_all_database
ap @cbsa2msa.all.to_a
ap @zip2cbsa.all.to_a
end
end
课程DbForCSV的工作原理如下:
class DbForCsv
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Attributes::Dynamic
def self.import!(file_path)
columns = []
instances = []
CSV.foreach(file_path, encoding: 'iso-8859-1:UTF-8') do |row|
if columns.empty?
# We dont want attributes with whitespaces
columns = row.collect { |c| c.downcase.gsub(' ', '_') }
next
end
instances << create!(build_attributes(row, columns))
end
instances
end
private
def self.build_attributes(row, columns)
attrs = {}
columns.each_with_index do |column, index|
attrs[column] = row[index]
end
ap attrs
attrs
end
end
我正在使用编码来确保只处理UTF8字符,但我仍然看到:
{
"zip" => "71964",
"cbsa" => "31680",
"res_ratio" => "0.086511098",
"bus_ratio" => "0.012048193",
"oth_ratio" => "0.000000000",
"tot_ratio" => "0.082435345"
}
在代码中执行'ap attrs'时。如何确保'zip' - &gt; '拉链'
我也试过了:
columns = row.collect { |c| c.encode(Encoding.find('UTF-8'), {invalid: :replace, undef: :replace, replace: ''}).downcase.gsub(' ', '_')}
但同样的事情
ArgumentError - invalid byte sequence in UTF-8
这是文件csv file
由于
答案 0 :(得分:5)
如果我从csv文件中读到你读的字:
zip
并将其粘贴到十六进制编辑器中,它显示该单词由字节组成:
z i p
| | |
V V V
C3 AF C2 BB C2 BF 7A 69 70
那么“zip”面前的垃圾是什么?
UTF-8 BOM是字符串:
"\xEF\xBB\xBF"
如果我强制将BOM字符串(在ruby程序中默认为UTF-8)编码为iso-8859-1:
"\xEF\xBB\xBF".force_encoding("ISO-8859-1")
然后查看iso-8859-1 chart这些十六进制代码,我发现:
EF => ï
BB => »
BF => ¿
接下来,如果我将BOM字符串编码为UTF-8:
"\xEF\xBB\xBF".force_encoding("ISO-8859-1").encode("UTF-8")
要求ruby用UTF-8编码中相同字符的十六进制转义符替换字符串中的十六进制转义符,它们是:
ï c3 af LATIN SMALL LETTER I WITH DIAERESIS
» c2 bb RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
¿ c2 bf INVERTED QUESTION MARK
给我:
"\xC3\xAF\xC2\xBB\xC2\xBF"
删除ruby的十六进制转义语法给了我:
C3 AF C2 BB C2 BF
将其与十六进制编辑器显示的内容进行比较:
z i p
| | |
V V V
C3 AF C2 BB C2 BF 7A 69 70
看起来很熟悉?
当你写这篇文章时,你要求ruby做同样的事情:
CSV.foreach(file_path, encoding: 'iso-8859-1:UTF-8')
在文件中,您在文件开头有一个UTF-8 BOM:
"\xEF\xBB\xBF"
但是,你告诉ruby该文件是用ISO-8859-1编码的,并且你希望ruby将文件转换为ruby程序中的UTF-8字符串:
external encoding
|
V
CSV.foreach(file_path, encoding: 'iso-8859-1:UTF-8')
^
|
internal encoding
因此ruby经历了与上述相同的过程,在ruby程序中生成一个如下所示的字符串:
"\xC3\xAF\xC2\xBB\xC2\xBF"
会搞砸你的第一行CSV数据。你说:
我正在使用编码来确保只处理UTF8字符
但这对我没有任何意义。如果文件是UTF-8,那么告诉ruby外部编码是UTF-8:
external encoding
|
V
CSV.foreach(file_path, encoding: 'UTF-8:UTF-8')
^
|
internal encoding
Ruby在读取文件时不会自动跳过BOM,因此您仍然可以在第一行的开头获得有趣的字符。要解决这个问题,您可以使用外部编码'BOM|UTF-8'
,它告诉ruby使用BOM(如果存在)来确定外部编码,然后跳过BOM;或者如果没有BOM,则使用'UTF-8'作为外部编码:
external encoding
|
V
CSV.foreach(file_path, encoding: 'BOM|UTF-8:UTF-8')
^
|
internal encoding
该编码适用于CSV.foreach()
,在CSV确定文件编码后,它会导致CSV跳过BOM。
对评论的回应:
您发布的文件不是UTF-8,也没有BOM。当您将外部编码指定为"BOM|UTF-8"
并且没有BOM时,您告诉CSV回退到UTF-8的外部编码,并且此行中出现CSV错误:
"Doña Ana County"
字符ñ
在文件中指定为F1
,它是字符ñ
的ISO-8559-1十六进制代码,并且没有随机的UTF-8字符使用编码F1
(在UTF-8中,带有TILDE的LATIN SMALL LETTER N的十六进制代码实际上是C3 B1
)。
如果您将外部编码更改为“ISO-8859-1”并将内部编码指定为“UTF-8”,则CSV将正确处理该文件,CSV将转换F1
读取从文件到C3 B1
并将您的程序交给UTF-8编码的字符串。 底线是:您必须知道要读取的文件的编码。如果您正在阅读许多文件并且它们都有不同的编码,那么您必须先知道每个文件的编码可以读它。如果您确定所有文件都是ISO-8859-1或UTF-8,那么您可以尝试使用一种编码读取文件,如果CSV错误输出,您可以捕获编码错误并尝试其他编码。
答案 1 :(得分:3)
你有BOM-marked UTF-8 file。 Ruby能够使用IO::new
{{3}}(以及mode
和IO::open
)的CSV::open
选项自动跳过BOM。不幸的是,AFAIK你不能让CSV::foreach
传递mode
参数,所以我们必须更加冗长。
替换
CSV.foreach(file_path, encoding: 'iso-8859-1:UTF-8') do |row|
# ...
end
与
CSV::open(file_path, 'r:bom|utf-8') do |csv|
csv.each do |row|
# ...
end
end
编辑:还有一种更好的方法可以从带有标题的CSV中读取哈希值(不需要if...next
,不需要build_attributes
...),因为Ruby的CSV库是相当强大:
instances = CSV::open(file_path, 'r:bom|utf-8',
headers: true,
header_converters: lambda { |c| c.downcase.gsub(' ', '_') }) do |csv|
csv.map do |row|
create!(row.to_hash)
end
end