Как я могу дополнительно обработать строку данных, которая заставляет библиотеку 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. Как я могу получить доступ к данным, вызвавшим ошибку, из условия восстановления?
3 ответа
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 может обернуть объект ввода-вывода. То, что вы могли бы сделать, это сделать свой собственный подкласс 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
Вроде аккуратно, правда? НО! Есть оговорки, конечно:
Я не уверен, какую долю успеха вы получаете, когда добавляете дополнительный шаг к каждому
gets
вызов. Это может быть проблемой, если вам нужно своевременно анализировать файлы с несколькими миллионами строк.это
терпит неудачу совершенноможет или не может произойти сбой, если ваш CSV-файл содержит символы новой строки внутри указанных полей. Причина этого описана в источнике - в основном, если значение в кавычках содержит символ новой строки, тоshift
должен сделать дополнительныйgets
звонки, чтобы получить всю линию. Может быть разумный способ обойти это ограничение, но оно не придет ко мне прямо сейчас. Если вы уверены, что в вашем файле нет символов новой строки в полях в кавычках, это не должно вас беспокоить.
Другой вариант - прочитать файл, используя File.gets
и передать каждую строку по очереди FasterCSV#parse_line
но я уверен, что при этом вы бы упустили любое преимущество в производительности, полученное при использовании FasterCSV.
Я использовал подход 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.