csv文件中的utf8字符

时间:2017-03-28 03:39:43

标签: ruby mongodb csv utf-8

我正在尝试使用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

由于

2 个答案:

答案 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}}(以及modeIO::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