module CsvParser
grammar Csv include ParserExtensions rule records non_empty_records / empty_records end rule non_empty_records first_record other_records { def value arr = [first_record.value] rest = other_records.value if rest arr.push(*rest) end arr end } end rule empty_records '' { def value [] end } end rule first_record '' &{ |s| @first_record = true; true } non_empty_record &{ |s| @first_record_length = @record_length; true } { def value non_empty_record.value end } end rule other_records '' &{ |s| @first_record = false; true } ( ( &{ |s| skip_empty_record? } ( record_sep ( empty_record record_sep )* non_empty_record )* ( &{ |s| skip_empty_record? } ( record_sep empty_record )+ )? { def value val = elements[1].elements.collect { |elt| elt.non_empty_record.value } val.empty? ? nil : val end } ) / ( &{ |s| !skip_empty_record? } ( record_sep record )* { def value val = elements[1].elements.collect { |elt| elt.record.value } val.empty? ? nil : val end } ) ) { def value elements[2].value end } end rule record non_empty_record / empty_record end rule non_empty_record first:field &{ |s| @record_length = 1; @warning = nil; true } rest:( &{ |s| if @first_record || @record_length < @first_record_length true else if allow_uneven_records? @warning ||= [:extra_fields, input.line_of(index + 1), input.column_of(index + 1)] true else @failure_type = :extra_fields false end end } field_sep field &{ |s| @record_length += 1; true } )* &{ |s| if @first_record || @record_length >= @first_record_length if @warning warnings << @warning end true else if allow_uneven_records? warnings << [:missing_fields, input.line_of(index), input.column_of(index)] true else @failure_type = :missing_fields false end end } { def value arr = [first.value] rest.elements.each do |elt| arr << elt.field.value end arr end } end rule empty_record '' &{ |s| if allow_empty_record? true else @failure_type = :missing_fields false end } { def value [] end } end rule field unquoted_text { def value elements.map(&:text_value).join end } / quoted_text { def value elements[1..-2].map(&:text_value).join end } end rule quoted_text quote ( !quote . )+ ( quote / '' !{ |s| @failure_type = :missing_quote; @failure_index = start_index; true } ) end rule unquoted_text ( !field_sep !record_sep ( !quote / '' !{ |s| @failure_type = :stray_quote; true } ) . )+ end rule field_sep &{ |s| @field_sep_index = 0; true } ( !record_sep !quote . &{ |s| if @field_sep_index < field_sep.length && s[2].text_value == field_sep[@field_sep_index] @field_sep_index += 1 true else false end } )+ &{ |s| s.map(&:text_value).join == field_sep } end rule record_sep &{ |s| @record_sep_index = 0; true } ( . &{ |s| if @record_sep_index < record_sep.length && s[0].text_value == record_sep[@record_sep_index] @record_sep_index += 1 true else false end } )+ &{ |s| s.map(&:text_value).join == record_sep } end rule quote !"\\" . &{ |s| s[1].text_value[0] == quote_char[0] } end end
end