如何进一步处理导致Ruby FasterCSV库抛出MalformedCSVError的数据行?

传入的数据文件包含格式错误的CSV数据(如非转义引号)以及(有效)CSV数据(如包含新行的字段)。 如果检测到CSV格式错误,我想对该数据使用替代例程。

使用以下示例代码(简称为简称)

FasterCSV.open( file ){|csv| row = true while row begin row = csv.shift break unless row # Do things with the good rows here... rescue FasterCSV::MalformedCSVError => e # Do things with the bad rows here... next end end } 

MalformedCSVError是在csv.shift方法中引起的。 如何从rescue子句中访问导致错误的数据?

 require 'csv' #CSV in ruby 1.9.2 is identical to FasterCSV # File.open('test.txt','r').each do |line| DATA.each do |line| begin CSV.parse(line) do |row| p row #handle row end rescue CSV::MalformedCSVError => er puts er.message puts "This one: #{line}" # and continue end end # Output: # Unclosed quoted field on line 1. # This one: 1,"aaa # Illegal quoting on line 1. # This one: aaa",valid # Unclosed quoted field on line 1. # This one: 2,"bbb # ["bbb", "invalid"] # ["3", "ccc", "valid"] __END__ 1,"aaa aaa",valid 2,"bbb bbb,invalid 3,ccc,valid 

只需将文件逐行提供给FasterCSV并挽救错误。

这真的很难。 使FasterCSV变得更快的一些事情使得这一点变得特别困难。 这是我最好的建议:FasterCSV可以包装一个IO对象。 那么,你可以做的是创建自己的File子类(本身是IO的子类),它“保留”最后gets的结果。 然后,当FasterCSV引发exception时,您可以向特殊的File对象询问最后一行。 像这样的东西:

 class MyFile < File attr_accessor :last_gets @last_gets = '' def gets(*args) line = super @last_gets << $/ << line line end end # then... file = MyFile.open(filename, 'r') csv = FasterCSV.new file row = true while row begin break unless row = csv.shift # do things with the good row here... rescue FasterCSV::MalformedCSVError => e bad_row = file.last_gets # do something with bad_row here... next ensure file.last_gets = '' # nuke the @last_gets "buffer" end end 

有点整洁,对吗? 但! 当然有一些警告:

  1. 我不确定当你为每次接听电话添加一个额外的步骤时,你的性能有多大。 如果您需要及时解析数百万行文件,这可能是一个问题。

  2. 这个 彻底失败了 如果您的CSV文件在引用字段中包含换行符,则可能会也可能不会失败。 其原因在源代码中描述 -基本上,如果引用的值包含换行符,则shift必须执行额外gets调用以获取整行。 可能有一个聪明的方法来解决这个限制,但现在还没有找到我。 如果您确定您的文件在引用字段中没有任何换行符,那么这不应该让您担心。

你的另一个选择是使用File.gets读取文件并依次将每一行传递给FasterCSV#parse_line但我很确定你会浪费使用FasterCSV获得的任何性能优势。

在CSV尝试解析之前,我使用Jordan的文件子类化方法来修复输入数据的问题。 就我而言,我有一个文件使用\“来转义引号,而不是CSV所期望的”。因此,

 class MyFile < File def gets(*args) line = super if line != nil line.gsub!('\\"','""') # fix the \" that would otherwise cause a parse error end line end end infile = MyFile.open(filename) incsv = CSV.new(infile) while row = infile.shift # process each row here end 

这允许我解析非标准的CSV文件。 Ruby的CSV实现非常严格,并且通常会遇到CSV格式的许多变体。