Skip to content

Commit 4848dc2

Browse files
committed
Increase speed of Table#to_csv
If we set the encoding when we call Table#to_csv we do not need to go row by row to determine the encoding to use. This allows the use of CSV#generate_lines as a faster exporter. Add a single_encoding option to Table#to_csv to use the encoding of the first row of the table as the encoding.
1 parent 650b9c2 commit 4848dc2

File tree

2 files changed

+33
-4
lines changed

2 files changed

+33
-4
lines changed

lib/csv/table.rb

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,8 @@ def to_a
987987
# :call-seq:
988988
# table.to_csv(**options) -> csv_string
989989
#
990+
# - Argument +single_encoding+, if true, encoding will be determined from the first row.
991+
#
990992
# Returns the table as \CSV string.
991993
# See {Options for Generating}[../CSV.html#class-CSV-label-Options+for+Generating].
992994
#
@@ -1001,15 +1003,32 @@ def to_a
10011003
#
10021004
# Limit rows if option +limit+ is given like +2+:
10031005
# table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n"
1004-
def to_csv(write_headers: true, limit: nil, **options)
1006+
def to_csv(write_headers: true, limit: nil, single_encoding: false, **options)
10051007
array = write_headers ? [headers.to_csv(**options)] : []
10061008
limit ||= @table.size
10071009
limit = @table.size + 1 + limit if limit < 0
10081010
limit = 0 if limit < 0
1009-
@table.first(limit).each do |row|
1010-
array.push(row.fields.to_csv(**options)) unless row.header_row?
1011-
end
10121011

1012+
if single_encoding || options[:encoding]
1013+
rows = @table.first(limit).select { |row| !row.header_row? }
1014+
unless options[:encoding]
1015+
fallback_encoding = nil
1016+
output_encoding = nil
1017+
rows.first.fields.each do |field|
1018+
next unless field.is_a?(String)
1019+
fallback_encoding ||= field.encoding
1020+
next if field.ascii_only?
1021+
output_encoding = field.encoding
1022+
break
1023+
end
1024+
options[:encoding] = output_encoding || fallback_encoding
1025+
end
1026+
array.push(CSV.generate_lines(rows, **options))
1027+
else
1028+
@table.first(limit).each do |row|
1029+
array.push(row.fields.to_csv(**options)) unless row.header_row?
1030+
end
1031+
end
10131032
array.join("")
10141033
end
10151034
alias_method :to_s, :to_csv

test/csv/test_table.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,16 @@ def test_to_csv_limit_negative_over
373373
CSV
374374
end
375375

376+
def test_to_csv_single_encoding
377+
rows = [ CSV::Row.new(%w{A}, ["\x00\xac".force_encoding("ASCII-8BIT")]),
378+
CSV::Row.new(%w{A}, ["\x00\xac"]) ]
379+
table = CSV::Table.new(rows)
380+
381+
assert_equal('ASCII-8BIT', table.to_csv(single_encoding: true).encoding.to_s)
382+
assert_equal('UTF-8', table.to_csv(encoding: 'UTF-8').encoding.to_s)
383+
assert_raises(Encoding::CompatibilityError) {table.to_csv}
384+
end
385+
376386
def test_append
377387
# verify that we can chain the call
378388
assert_equal(@table, @table << [10, 11, 12])

0 commit comments

Comments
 (0)