Как я могу дополнительно обработать строку данных, которая заставляет библиотеку Ruby FasterCSV выдавать MalformedCSVError? - PullRequest
4 голосов
/ 06 октября 2011

Входящие файлы данных содержат некорректные данные 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.Как получить доступ к данным, вызвавшим ошибку, из условия восстановления?

Ответы [ 3 ]

8 голосов
/ 06 октября 2011
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 и спасите ошибку.

2 голосов
/ 06 октября 2011

Это будет действительно сложно. Некоторые вещи, которые делают FasterCSV, ну, быстрее , делают это особенно трудно. Вот мое лучшее предложение: FasterCSV может обернуть объект IO . То, что вы могли бы сделать, это сделать свой собственный подкласс File (сам подкласс IO), который "держит" результат последнего 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.

1 голос
/ 03 октября 2012

Я использовал подход Jordan для создания подклассов файлов, чтобы исправить проблему с моими входными данными до того, как CSV попытается их проанализировать. В моем случае у меня был файл, который использовал \ "для экранирования кавычек вместо" ", который ожидает 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. Реализация CSV в Ruby очень строгая и часто имеет проблемы со многими вариантами формата CSV.

...