4

传入的数据文件包含格式错误的 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 方法中引起的。如何从救援子句访问导致错误的数据?

4

3 回答 3

8
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 并挽救错误。

于 2011-10-06T08:04:18.287 回答
2

这将是非常困难的。一些使 FasterCSV 更好、更快的东西使这变得特别困难。这是我最好的建议:FasterCSV 可以包装一个IO对象。那么,您可以做的是创建自己的子类File(本身是 的子类IO),以“保留” last 的结果gets。然后,当 FasterCSV 引发异常时,您可以向您的特殊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. 我不确定当您在每次gets调用中添加额外步骤时,您会受到多少性能影响。如果您需要及时解析数百万行的文件,这可能是个问题。

  2. 这个彻底失败如果您的 CSV 文件在带引号的字段中包含换行符,则可能会失败,也可能不会失败。原因在源代码中有所描述——基本上,如果引用的值包含换行符,则shift必须进行额外gets的调用以获取整行。可能有一种巧妙的方法可以绕过这个限制,但现在我还没有想到。如果您确定您的文件在带引号的字段中没有任何换行符,那么您不必担心。

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

于 2011-10-06T08:05:41.753 回答
1

在 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 格式的许多变体的问题。

于 2012-10-02T23:54:22.003 回答