Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions lib/csv/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -731,11 +731,11 @@ def resolve_row_separator(separator)
end

def detect_row_separator(sample, cr, lf)
lf_index = sample.index(lf)
lf_index = unquoted_index(sample, lf)
if lf_index
cr_index = sample[0, lf_index].index(cr)
cr_index = unquoted_index(sample[0, lf_index], cr)
else
cr_index = sample.index(cr)
cr_index = unquoted_index(sample, cr)
end
if cr_index and lf_index
if cr_index + 1 == lf_index
Expand All @@ -754,6 +754,21 @@ def detect_row_separator(sample, cr, lf)
end
end

# Index of the first +target+ that is outside a quoted field, so a CR or
# LF inside quotes is not mistaken for the row separator.
def unquoted_index(sample, target)
return sample.index(target) if @quote_character.nil?
in_quote = false
sample.each_char.with_index do |char, i|
if char == @quote_character
in_quote = !in_quote
elsif char == target and not in_quote
return i
end
end
nil
end

def prepare_line
@lineno = 0
@last_line = nil
Expand Down
29 changes: 29 additions & 0 deletions test/csv/parse/test_row_separator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,33 @@ def test_multiple_characters
CSV.parse("a\r\nb\r\n", row_sep: "\r\n"))
end
end

# :auto detection must ignore CR/LF inside a quoted field.
def test_auto_quoted_cr
assert_equal([["a\rb", "x"]],
CSV.parse("\"a\rb\",x\n"))
end

def test_auto_quoted_cr_lf
assert_equal([["a\r\nb", "x"]],
CSV.parse("\"a\r\nb\",x\n"))
end

def test_auto_quoted_cr_only_field
assert_equal([["\r"]],
CSV.parse("\"\r\"\n"))
end

# An unquoted CR is still a valid (old Mac) row separator.
def test_auto_unquoted_cr_still_detected
assert_equal([["a"], ["b"]],
CSV.parse("a\rb\r"))
end

# CSV must be able to parse its own generated output.
def test_auto_round_trip_cr_fields
rows = [["a\rb", "x"], ["\r", "y"], ["c", "d\r\ne"]]
generated = CSV.generate {|csv| rows.each {|row| csv << row}}
assert_equal(rows, CSV.parse(generated))
end
end