summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog13
-rw-r--r--lib/csv.rb124
-rw-r--r--test/csv/mac.csv2
-rw-r--r--test/csv/test_csv.rb365
4 files changed, 327 insertions, 177 deletions
diff --git a/ChangeLog b/ChangeLog
index 9b915bc728..e49bb3ce85 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+Mon Sep 15 19:02:52 2003 NAKAMURA, Hiroshi <nahi@ruby-lang.org>
+
+ * lib/csv.rb: add extra pamameter to specify row(record) separater
+ character. To parse Mac's CR separated CSV, do like this.
+ CSV.open("mac.csv", "r", ?,, ?\r) { |row| p row.to_a }
+ The 3rd parameter in this example ?, is for column separater and the
+ 4th ?\r is for row separater. Row separater is nil by default. Nil
+ separater means "\r\n" or "\n".
+
+ * test/csv/test_csv.rb: add tests for above feature.
+
+ * test/csv/mac.csv: added. Sample CR separated CSV file.
+
Fri Sep 12 22:41:48 2003 Michal Rokos <m.rokos@sh.cvut.cz>
* ext/openssl/ossl.c: move ASN.1 stuff to ossl_asn1.[ch]
diff --git a/lib/csv.rb b/lib/csv.rb
index 947eacbcfa..ee686db0cf 100644
--- a/lib/csv.rb
+++ b/lib/csv.rb
@@ -189,32 +189,32 @@ public
# writer << [nil, nil]
# end
#
- def CSV.open(filename, mode, col_sep = ?,, &block)
+ def CSV.open(filename, mode, col_sep = ?,, row_sep = nil, &block)
if mode == 'r' or mode == 'rb'
- open_reader(filename, col_sep, &block)
+ open_reader(filename, col_sep, row_sep, &block)
elsif mode == 'w' or mode == 'wb'
- open_writer(filename, col_sep, &block)
+ open_writer(filename, col_sep, row_sep, &block)
else
raise ArgumentError.new("'mode' must be 'r', 'rb', 'w', or 'wb'")
end
end
- def CSV.parse(filename, col_sep = ?,, &block)
- open_reader(filename, col_sep, &block)
+ def CSV.parse(filename, col_sep = ?,, row_sep = nil, &block)
+ open_reader(filename, col_sep, row_sep, &block)
end
- def CSV.generate(filename, col_sep = ?,, &block)
- open_writer(filename, col_sep, &block)
+ def CSV.generate(filename, col_sep = ?,, row_sep = nil, &block)
+ open_writer(filename, col_sep, row_sep, &block)
end
# Private class methods.
class << self
private
- def open_reader(filename, col_sep, &block)
+ def open_reader(filename, col_sep, row_sep, &block)
file = File.open(filename, 'rb')
if block
begin
- CSV::Reader.parse(file, col_sep) do |row|
+ CSV::Reader.parse(file, col_sep, row_sep) do |row|
yield(row)
end
ensure
@@ -222,17 +222,17 @@ public
end
nil
else
- reader = CSV::Reader.create(file, col_sep)
+ reader = CSV::Reader.create(file, col_sep, row_sep)
reader.close_on_terminate
reader
end
end
- def open_writer(filename, col_sep, &block)
+ def open_writer(filename, col_sep, row_sep, &block)
file = File.open(filename, 'wb')
if block
begin
- CSV::Writer.generate(file, col_sep) do |writer|
+ CSV::Writer.generate(file, col_sep, row_sep) do |writer|
yield(writer)
end
ensure
@@ -240,7 +240,7 @@ public
end
nil
else
- writer = CSV::Writer.create(file, col_sep)
+ writer = CSV::Writer.create(file, col_sep, row_sep)
writer.close_on_terminate
writer
end
@@ -275,14 +275,14 @@ public
# DESCRIPTION
# Create instance. To get parse result, see CSV::Reader#each.
#
- def Reader.create(str_or_readable, col_sep = ?,)
+ def Reader.create(str_or_readable, col_sep = ?,, row_sep = nil)
case str_or_readable
when IO
- IOReader.new(str_or_readable, col_sep)
+ IOReader.new(str_or_readable, col_sep, row_sep)
when String
- StringReader.new(str_or_readable, col_sep)
+ StringReader.new(str_or_readable, col_sep, row_sep)
else
- IOReader.new(str_or_readable, col_sep)
+ IOReader.new(str_or_readable, col_sep, row_sep)
end
end
@@ -305,8 +305,8 @@ public
# Block value is always nil. Rows are not cached for performance
# reason.
#
- def Reader.parse(str_or_readable, col_sep = ?,)
- reader = create(str_or_readable, col_sep)
+ def Reader.parse(str_or_readable, col_sep = ?,, row_sep = nil)
+ reader = create(str_or_readable, col_sep, row_sep)
reader.each do |row|
yield(row)
end
@@ -413,8 +413,9 @@ public
# DESCRIPTION
# Create instance. To get parse result, see CSV::Reader#each.
#
- def initialize(string, col_sep = ?,)
+ def initialize(string, col_sep = ?,, row_sep = nil)
@col_sep = col_sep
+ @row_sep = row_sep
@dev = string
@idx = 0
if @dev[0, 3] == "\xef\xbb\xbf"
@@ -424,7 +425,7 @@ public
private
def get_row(row)
- parsed_cells, next_idx = CSV.parse_row(@dev, @idx, row, @col_sep)
+ parsed_cells, next_idx = CSV.parse_row(@dev, @idx, row, @col_sep, @row_sep)
if parsed_cells == 0 && next_idx == 0 && @idx != @dev.size
raise IllegalFormatError.new
end
@@ -460,9 +461,10 @@ public
# DESCRIPTION
# Create instance. To get parse result, see CSV::Reader#each.
#
- def initialize(io, col_sep = ?,)
+ def initialize(io, col_sep = ?,, row_sep = nil)
@io = io
@col_sep = col_sep
+ @row_sep = row_sep
@dev = CSV::IOBuf.new(@io)
@idx = 0
if @dev[0] == 0xef and @dev[1] == 0xbb and @dev[2] == 0xbf
@@ -487,7 +489,7 @@ public
private
def get_row(row)
- parsed_cells, next_idx = CSV.parse_row(@dev, @idx, row, @col_sep)
+ parsed_cells, next_idx = CSV.parse_row(@dev, @idx, row, @col_sep, @row_sep)
if parsed_cells == 0 && next_idx == 0 && !@dev.is_eos?
raise IllegalFormatError.new
end
@@ -549,8 +551,8 @@ public
# Create instance. To add CSV data to generate CSV string, see
# CSV::Writer#<< or CSV::Writer#add_row.
#
- def Writer.create(str_or_readable, col_sep = ?,)
- BasicWriter.new(str_or_readable, col_sep)
+ def Writer.create(str_or_readable, col_sep = ?,, row_sep = nil)
+ BasicWriter.new(str_or_readable, col_sep, row_sep)
end
# SYNOPSIS
@@ -572,8 +574,8 @@ public
# To add CSV data to generate CSV string, see CSV::Writer#<< or
# CSV::Writer#add_row.
#
- def Writer.generate(str_or_writable, col_sep = ?,)
- writer = Writer.create(str_or_writable, col_sep)
+ def Writer.generate(str_or_writable, col_sep = ?,, row_sep = nil)
+ writer = Writer.create(str_or_writable, col_sep, row_sep)
yield(writer)
writer.close
nil
@@ -602,7 +604,7 @@ public
Cell.new(item.to_s, false)
end
}
- CSV.generate_row(row, row.size, @dev, @col_sep)
+ CSV.generate_row(row, row.size, @dev, @col_sep, @row_sep)
self
end
@@ -621,7 +623,7 @@ public
# (Formar is 'c1' and latter is Null.)
#
def add_row(row)
- CSV.generate_row(row, row.size, @dev, @col_sep)
+ CSV.generate_row(row, row.size, @dev, @col_sep, @row_sep)
self
end
@@ -669,8 +671,9 @@ public
# Create instance. To add CSV data to generate CSV string, see
# CSV::Writer#<< or CSV::Writer#add_row.
#
- def initialize(str_or_writable, col_sep = ?,)
+ def initialize(str_or_writable, col_sep = ?,, row_sep = nil)
@col_sep = col_sep
+ @row_sep = row_sep
@dev = str_or_writable
@close_on_terminate = false
end
@@ -698,12 +701,14 @@ public
end
# SYNOPSIS
- # cells = CSV.parse_line(src, col_sep = ?,)
+ # cells = CSV.parse_line(src, col_sep = ?,, row_sep = nil)
#
# ARGS
# src: a CSV String.
# col_sep: Column separator. ?, by default. If you want to separate
# fields with semicolon, give ?; here.
+ # row_sep: Row separator. nil by default. nil means "\r\n or \n". If you
+ # want to separate records with \r, give ?\r here.
#
# RETURNS
# cells: an Array of parsed cells in first line. Each cell is a String.
@@ -716,14 +721,14 @@ public
# If you don't know whether a target string to parse is exactly 1 line or
# not, use CSV.parse_row instead of this method.
#
- def CSV.parse_line(src, col_sep = ?,)
+ def CSV.parse_line(src, col_sep = ?,, row_sep = nil)
idx = 0
res_type = :DT_COLSEP
cells = Row.new
begin
while (res_type.equal?(:DT_COLSEP))
cell = Cell.new
- res_type, idx = parse_body(src, idx, cell, col_sep)
+ res_type, idx = parse_body(src, idx, cell, col_sep, row_sep)
cells.push(cell.is_null ? nil : cell.data)
end
rescue IllegalFormatError
@@ -734,13 +739,15 @@ public
# SYNOPSIS
- # str = CSV.generate_line(cells, col_sep = ?,)
+ # str = CSV.generate_line(cells, col_sep = ?,, row_sep = nil)
#
# ARGS
# cells: an Array of cell to be converted to CSV string. Each cell must
# respond to 'to_s'.
# col_sep: Column separator. ?, by default. If you want to separate
# fields with semicolon, give ?; here.
+ # row_sep: Row separator. nil by default. nil means "\r\n or \n". If you
+ # want to separate records with \r, give ?\r here.
#
# RETURNS
# str: a String of generated CSV string.
@@ -748,7 +755,7 @@ public
# DESCRIPTION
# Create a line from cells. Each cell is stringified by to_s.
#
- def CSV.generate_line(cells, col_sep = ?,)
+ def CSV.generate_line(cells, col_sep = ?,, row_sep = nil)
if (cells.size == 0)
return ''
end
@@ -761,18 +768,18 @@ public
else
Cell.new(cells[idx].to_s, false)
end
- generate_body(cell, result_str, col_sep)
+ generate_body(cell, result_str, col_sep, row_sep)
idx += 1
if (idx == cells.size)
break
end
- generate_separator(:DT_COLSEP, result_str, col_sep)
+ generate_separator(:DT_COLSEP, result_str, col_sep, row_sep)
end
result_str
end
# SYNOPSIS
- # parsed_cells, idx = CSV.parse_row(src, idx, out_dev, col_sep = ?,)
+ # parsed_cells, idx = CSV.parse_row(src, idx, out_dev, col_sep = ?,, row_sep = nil)
#
# ARGS
# src: a CSV data to be parsed. Must respond '[](idx)'.
@@ -783,6 +790,8 @@ public
# out_dev: buffer for parsed cells. Must respond '<<(CSV::Cell)'.
# col_sep: Column separator. ?, by default. If you want to separate
# fields with semicolon, give ?; here.
+ # row_sep: Row separator. nil by default. nil means "\r\n or \n". If you
+ # want to separate records with \r, give ?\r here.
#
# RETURNS
# parsed_cells: num of parsed cells.
@@ -802,14 +811,14 @@ public
# p parsed
# end while parsed_cells > 0
#
- def CSV.parse_row(src, idx, out_dev, col_sep = ?,)
+ def CSV.parse_row(src, idx, out_dev, col_sep = ?,, row_sep = nil)
idx_backup = idx
parsed_cells = 0
res_type = :DT_COLSEP
begin
while (!res_type.equal?(:DT_ROWSEP))
cell = Cell.new
- res_type, idx = parse_body(src, idx, cell, col_sep)
+ res_type, idx = parse_body(src, idx, cell, col_sep, row_sep)
if res_type.equal?(:DT_EOS)
if idx == idx_backup #((parsed_cells == 0) && (cell.is_null))
return 0, 0
@@ -826,7 +835,7 @@ public
end
# SYNOPSIS
- # parsed_cells = CSV.generate_row(src, cells, out_dev, col_sep = ?,)
+ # parsed_cells = CSV.generate_row(src, cells, out_dev, col_sep = ?,, row_sep = nil)
#
# ARGS
# src: an Array of CSV::Cell to be converted to CSV string. Must respond to
@@ -835,6 +844,8 @@ public
# out_dev: buffer for generated CSV string. Must respond to '<<(string)'.
# col_sep: Column separator. ?, by default. If you want to separate
# fields with semicolon, give ?; here.
+ # row_sep: Row separator. nil by default. nil means "\r\n or \n". If you
+ # want to separate records with \r, give ?\r here.
#
# RETURNS
# parsed_cells: num of converted cells.
@@ -859,27 +870,27 @@ public
# end
# p buf
#
- def CSV.generate_row(src, cells, out_dev, col_sep = ?,)
+ def CSV.generate_row(src, cells, out_dev, col_sep = ?,, row_sep = nil)
src_size = src.size
if (src_size == 0)
if cells == 0
- generate_separator(:DT_ROWSEP, out_dev, col_sep)
+ generate_separator(:DT_ROWSEP, out_dev, col_sep, row_sep)
end
return 0
end
res_type = :DT_COLSEP
parsed_cells = 0
- generate_body(src[parsed_cells], out_dev, col_sep)
+ generate_body(src[parsed_cells], out_dev, col_sep, row_sep)
parsed_cells += 1
while ((parsed_cells < cells) && (parsed_cells != src_size))
- generate_separator(:DT_COLSEP, out_dev, col_sep)
- generate_body(src[parsed_cells], out_dev, col_sep)
+ generate_separator(:DT_COLSEP, out_dev, col_sep, row_sep)
+ generate_body(src[parsed_cells], out_dev, col_sep, row_sep)
parsed_cells += 1
end
if (parsed_cells == cells)
- generate_separator(:DT_ROWSEP, out_dev, col_sep)
+ generate_separator(:DT_ROWSEP, out_dev, col_sep, row_sep)
else
- generate_separator(:DT_COLSEP, out_dev, col_sep)
+ generate_separator(:DT_COLSEP, out_dev, col_sep, row_sep)
end
parsed_cells
end
@@ -891,7 +902,8 @@ private
class << self
private
- def parse_body(src, idx, cell, col_sep)
+ def parse_body(src, idx, cell, col_sep, row_sep)
+ row_sep_end = row_sep || ?\n
cell.is_null = false
state = :ST_START
quoted = false
@@ -941,7 +953,7 @@ private
quoted = true
state = :ST_DATA
end
- elsif (c == ?\r)
+ elsif row_sep.nil? and c == ?\r
if cr
raise IllegalFormatError.new
end
@@ -951,7 +963,7 @@ private
else
cr = true
end
- elsif (c == ?\n)
+ elsif c == row_sep_end
if state.equal?(:ST_DATA)
if cr
state = :ST_END
@@ -1004,12 +1016,14 @@ private
return :DT_EOS, idx
end
- def generate_body(cells, out_dev, col_sep)
+ def generate_body(cells, out_dev, col_sep, row_sep)
row_data = cells.data.dup
if (!cells.is_null)
if (row_data.gsub!('"', '""') ||
row_data.include?(col_sep) ||
- (/[\r\n]/ =~ row_data) || (cells.data.empty?))
+ (row_sep && row_data.index(row_sep)) ||
+ (/[\r\n]/ =~ row_data) ||
+ (cells.data.empty?))
out_dev << '"' << row_data << '"'
else
out_dev << row_data
@@ -1017,12 +1031,12 @@ private
end
end
- def generate_separator(type, out_dev, col_sep)
+ def generate_separator(type, out_dev, col_sep, row_sep)
case type
when :DT_COLSEP
out_dev << col_sep.chr
when :DT_ROWSEP
- out_dev << "\r\n"
+ out_dev << (row_sep || "\r\n")
end
end
end
diff --git a/test/csv/mac.csv b/test/csv/mac.csv
new file mode 100644
index 0000000000..95b1a1725b
--- /dev/null
+++ b/test/csv/mac.csv
@@ -0,0 +1,2 @@
+"Avenches","aus Umgebung"
+"Bad Hersfeld","Ausgrabung" \ No newline at end of file
diff --git a/test/csv/test_csv.rb b/test/csv/test_csv.rb
index f1ba35a66f..41e25fe5ec 100644
--- a/test/csv/test_csv.rb
+++ b/test/csv/test_csv.rb
@@ -1,4 +1,5 @@
-require 'test/unit'
+require 'test/unit/testsuite'
+require 'test/unit/testcase'
require 'tempfile'
require 'fileutils'
@@ -12,130 +13,16 @@ class CSV
end
end
-class TestCSV < Test::Unit::TestCase
-
- class << self
- def d(data, is_null = false)
- CSV::Cell.new(data.to_s, is_null)
- end
- end
-
- @@colData = ['', nil, true, false, 'foo', '!' * 1000]
- @@simpleCSVData = {
- [nil] => '',
- [''] => '""',
- [nil, nil] => ',',
- [nil, nil, nil] => ',,',
- ['foo'] => 'foo',
- [','] => '","',
- [',', ','] => '",",","',
- [';'] => ';',
- [';', ';'] => ';,;',
- ["\"\r", "\"\r"] => "\"\"\"\r\",\"\"\"\r\"",
- ["\"\n", "\"\n"] => "\"\"\"\n\",\"\"\"\n\"",
- ["\t"] => "\t",
- ["\t", "\t"] => "\t,\t",
- ['foo', 'bar'] => 'foo,bar',
- ['foo', '"bar"', 'baz'] => 'foo,"""bar""",baz',
- ['foo', 'foo,bar', 'baz'] => 'foo,"foo,bar",baz',
- ['foo', '""', 'baz'] => 'foo,"""""",baz',
- ['foo', '', 'baz'] => 'foo,"",baz',
- ['foo', nil, 'baz'] => 'foo,,baz',
- [nil, 'foo', 'bar'] => ',foo,bar',
- ['foo', 'bar', nil] => 'foo,bar,',
- ['foo', "\r", 'baz'] => "foo,\"\r\",baz",
- ['foo', "\n", 'baz'] => "foo,\"\n\",baz",
- ['foo', "\r\n\r", 'baz'] => "foo,\"\r\n\r\",baz",
- ['foo', "\r\n", 'baz'] => "foo,\"\r\n\",baz",
- ['foo', "\r.\n", 'baz'] => "foo,\"\r.\n\",baz",
- ['foo', "\r\n\n", 'baz'] => "foo,\"\r\n\n\",baz",
- ['foo', '"', 'baz'] => 'foo,"""",baz',
- }
-
- @@fullCSVData = {
- [d('', true)] => '',
- [d('')] => '""',
- [d('', true), d('', true)] => ',',
- [d('', true), d('', true), d('', true)] => ',,',
- [d('foo')] => 'foo',
- [d('foo'), d('bar')] => 'foo,bar',
- [d('foo'), d('"bar"'), d('baz')] => 'foo,"""bar""",baz',
- [d('foo'), d('foo,bar'), d('baz')] => 'foo,"foo,bar",baz',
- [d('foo'), d('""'), d('baz')] => 'foo,"""""",baz',
- [d('foo'), d(''), d('baz')] => 'foo,"",baz',
- [d('foo'), d('', true), d('baz')] => 'foo,,baz',
- [d('foo'), d("\r"), d('baz')] => "foo,\"\r\",baz",
- [d('foo'), d("\n"), d('baz')] => "foo,\"\n\",baz",
- [d('foo'), d("\r\n"), d('baz')] => "foo,\"\r\n\",baz",
- [d('foo'), d("\r.\n"), d('baz')] => "foo,\"\r.\n\",baz",
- [d('foo'), d("\r\n\n"), d('baz')] => "foo,\"\r\n\n\",baz",
- [d('foo'), d('"'), d('baz')] => 'foo,"""",baz',
- }
-
- @@fullCSVDataArray = @@fullCSVData.collect { |key, value| key }
-
- def ssv2csv(ssvStr)
- sepConv(ssvStr, ?;, ?,)
- end
-
- def csv2ssv(csvStr)
- sepConv(csvStr, ?,, ?;)
- end
- def tsv2csv(tsvStr)
- sepConv(tsvStr, ?\t, ?,)
- end
-
- def csv2tsv(csvStr)
- sepConv(csvStr, ?,, ?\t)
- end
-
- def sepConv(srcStr, srcSep, destSep)
- rows = CSV::Row.new
- cols, idx = CSV.parse_row(srcStr, 0, rows, srcSep)
- destStr = ''
- cols = CSV.generate_row(rows, rows.size, destStr, destSep)
- destStr
- end
-
-public
-
- def setup
- @tmpdir = File.join(Dir.tmpdir, "ruby_test_csv_tmp_#{$$}")
- Dir.mkdir(@tmpdir)
- @infile = File.join(@tmpdir, 'in.csv')
- @infiletsv = File.join(@tmpdir, 'in.tsv')
- @emptyfile = File.join(@tmpdir, 'empty.csv')
- @outfile = File.join(@tmpdir, 'out.csv')
- @bomfile = File.join(File.dirname(__FILE__), "bom.csv")
-
- CSV.open(@infile, "w") do |writer|
- @@fullCSVDataArray.each do |row|
- writer.add_row(row)
- end
- end
-
- CSV.open(@infiletsv, "w", ?\t) do |writer|
- @@fullCSVDataArray.each do |row|
- writer.add_row(row)
- end
- end
-
- CSV.generate(@emptyfile) do |writer|
- # Create empty file.
- end
- end
-
- def teardown
- FileUtils.rm_rf(@tmpdir)
- end
-
- def d(*arg)
- TestCSV.d(*arg)
+module CSVTestSupport
+ def d(data, is_null = false)
+ CSV::Cell.new(data.to_s, is_null)
end
+end
- #### CSV::Cell unit test
+class TestCSVCell < Test::Unit::TestCase
+ @@colData = ['', nil, true, false, 'foo', '!' * 1000]
def test_Cell_EQUAL # '=='
d1 = CSV::Cell.new('d', false)
@@ -206,9 +93,11 @@ public
d3 = CSV::Cell.new(nil, false)
assert_equal(d3.is_null, false, "Data: false.")
end
+end
- #### CSV::Row unit test
+class TestCSVRow < Test::Unit::TestCase
+ include CSVTestSupport
def test_Row_s_match
c1 = CSV::Row[d(1), d(2), d(3)]
@@ -267,7 +156,125 @@ public
r = CSV::Row[]
assert_equal([], r.to_a, 'Empty')
end
+end
+
+
+class TestCSV < Test::Unit::TestCase
+ include CSVTestSupport
+
+ class << self
+ include CSVTestSupport
+ end
+
+ @@simpleCSVData = {
+ [nil] => '',
+ [''] => '""',
+ [nil, nil] => ',',
+ [nil, nil, nil] => ',,',
+ ['foo'] => 'foo',
+ [','] => '","',
+ [',', ','] => '",",","',
+ [';'] => ';',
+ [';', ';'] => ';,;',
+ ["\"\r", "\"\r"] => "\"\"\"\r\",\"\"\"\r\"",
+ ["\"\n", "\"\n"] => "\"\"\"\n\",\"\"\"\n\"",
+ ["\t"] => "\t",
+ ["\t", "\t"] => "\t,\t",
+ ['foo', 'bar'] => 'foo,bar',
+ ['foo', '"bar"', 'baz'] => 'foo,"""bar""",baz',
+ ['foo', 'foo,bar', 'baz'] => 'foo,"foo,bar",baz',
+ ['foo', '""', 'baz'] => 'foo,"""""",baz',
+ ['foo', '', 'baz'] => 'foo,"",baz',
+ ['foo', nil, 'baz'] => 'foo,,baz',
+ [nil, 'foo', 'bar'] => ',foo,bar',
+ ['foo', 'bar', nil] => 'foo,bar,',
+ ['foo', "\r", 'baz'] => "foo,\"\r\",baz",
+ ['foo', "\n", 'baz'] => "foo,\"\n\",baz",
+ ['foo', "\r\n\r", 'baz'] => "foo,\"\r\n\r\",baz",
+ ['foo', "\r\n", 'baz'] => "foo,\"\r\n\",baz",
+ ['foo', "\r.\n", 'baz'] => "foo,\"\r.\n\",baz",
+ ['foo', "\r\n\n", 'baz'] => "foo,\"\r\n\n\",baz",
+ ['foo', '"', 'baz'] => 'foo,"""",baz',
+ }
+
+ @@fullCSVData = {
+ [d('', true)] => '',
+ [d('')] => '""',
+ [d('', true), d('', true)] => ',',
+ [d('', true), d('', true), d('', true)] => ',,',
+ [d('foo')] => 'foo',
+ [d('foo'), d('bar')] => 'foo,bar',
+ [d('foo'), d('"bar"'), d('baz')] => 'foo,"""bar""",baz',
+ [d('foo'), d('foo,bar'), d('baz')] => 'foo,"foo,bar",baz',
+ [d('foo'), d('""'), d('baz')] => 'foo,"""""",baz',
+ [d('foo'), d(''), d('baz')] => 'foo,"",baz',
+ [d('foo'), d('', true), d('baz')] => 'foo,,baz',
+ [d('foo'), d("\r"), d('baz')] => "foo,\"\r\",baz",
+ [d('foo'), d("\n"), d('baz')] => "foo,\"\n\",baz",
+ [d('foo'), d("\r\n"), d('baz')] => "foo,\"\r\n\",baz",
+ [d('foo'), d("\r.\n"), d('baz')] => "foo,\"\r.\n\",baz",
+ [d('foo'), d("\r\n\n"), d('baz')] => "foo,\"\r\n\n\",baz",
+ [d('foo'), d('"'), d('baz')] => 'foo,"""",baz',
+ }
+
+ @@fullCSVDataArray = @@fullCSVData.collect { |key, value| key }
+
+ def ssv2csv(ssvStr, row_sep = nil)
+ sepConv(ssvStr, ?;, ?,, row_sep)
+ end
+
+ def csv2ssv(csvStr, row_sep = nil)
+ sepConv(csvStr, ?,, ?;, row_sep)
+ end
+
+ def tsv2csv(tsvStr, row_sep = nil)
+ sepConv(tsvStr, ?\t, ?,, row_sep)
+ end
+
+ def csv2tsv(csvStr, row_sep = nil)
+ sepConv(csvStr, ?,, ?\t, row_sep)
+ end
+
+ def sepConv(srcStr, srcSep, destSep, row_sep = nil)
+ rows = CSV::Row.new
+ cols, idx = CSV.parse_row(srcStr, 0, rows, srcSep, row_sep)
+ destStr = ''
+ cols = CSV.generate_row(rows, rows.size, destStr, destSep, row_sep)
+ destStr
+ end
+
+public
+
+ def setup
+ @tmpdir = File.join(Dir.tmpdir, "ruby_test_csv_tmp_#{$$}")
+ Dir.mkdir(@tmpdir)
+ @infile = File.join(@tmpdir, 'in.csv')
+ @infiletsv = File.join(@tmpdir, 'in.tsv')
+ @emptyfile = File.join(@tmpdir, 'empty.csv')
+ @outfile = File.join(@tmpdir, 'out.csv')
+ @bomfile = File.join(File.dirname(__FILE__), "bom.csv")
+ @macfile = File.join(File.dirname(__FILE__), "mac.csv")
+
+ CSV.open(@infile, "w") do |writer|
+ @@fullCSVDataArray.each do |row|
+ writer.add_row(row)
+ end
+ end
+
+ CSV.open(@infiletsv, "w", ?\t) do |writer|
+ @@fullCSVDataArray.each do |row|
+ writer.add_row(row)
+ end
+ end
+
+ CSV.generate(@emptyfile) do |writer|
+ # Create empty file.
+ end
+ end
+ def teardown
+ FileUtils.rm_rf(@tmpdir)
+ end
#### CSV::Reader unit test
@@ -726,6 +733,11 @@ public
assert_equal("\r\n", buf, "Extra boundary check.")
buf = ''
+ cols = CSV.generate_row([], 0, buf, ?\t, ?|)
+ assert_equal(0, cols)
+ assert_equal("|", buf, "Extra boundary check.")
+
+ buf = ''
cols = CSV.generate_row([d(1)], 2, buf)
assert_equal('1,', buf)
@@ -738,6 +750,10 @@ public
assert_equal("1\t", buf)
buf = ''
+ cols = CSV.generate_row([d(1)], 2, buf, ?\t, ?|)
+ assert_equal("1\t", buf)
+
+ buf = ''
cols = CSV.generate_row([d(1), d(2)], 1, buf)
assert_equal("1\r\n", buf)
@@ -749,6 +765,18 @@ public
cols = CSV.generate_row([d(1), d(2)], 1, buf, ?\t)
assert_equal("1\r\n", buf)
+ buf = ''
+ cols = CSV.generate_row([d(1), d(2)], 1, buf, ?\t, ?\n)
+ assert_equal("1\n", buf)
+
+ buf = ''
+ cols = CSV.generate_row([d(1), d(2)], 1, buf, ?\t, ?\r)
+ assert_equal("1\r", buf)
+
+ buf = ''
+ cols = CSV.generate_row([d(1), d(2)], 1, buf, ?\t, ?|)
+ assert_equal("1|", buf)
+
@@fullCSVData.each do |col, str|
buf = ''
cols = CSV.generate_row(col, col.size, buf)
@@ -770,6 +798,22 @@ public
assert_equal(str + "\r\n", tsv2csv(buf))
end
+ # row separator
+ @@fullCSVData.each do |col, str|
+ buf = ''
+ cols = CSV.generate_row(col, col.size, buf, ?,, ?|)
+ assert_equal(col.size, cols)
+ assert_equal(str + "|", buf)
+ end
+
+ # col and row separator
+ @@fullCSVData.each do |col, str|
+ buf = ''
+ cols = CSV.generate_row(col, col.size, buf, ?\t, ?|)
+ assert_equal(col.size, cols)
+ assert_equal(str + "|", tsv2csv(buf, ?|))
+ end
+
buf = ''
toBe = ''
cols = 0
@@ -809,6 +853,20 @@ public
end
assert_equal(colsToBe, cols)
assert_equal(toBe, buf)
+
+ buf = ''
+ toBe = ''
+ cols = 0
+ colsToBe = 0
+ @@fullCSVData.each do |col, str|
+ lineBuf = ''
+ cols += CSV.generate_row(col, col.size, lineBuf, ?|)
+ buf << tsv2csv(lineBuf, ?|)
+ toBe << tsv2csv(lineBuf, ?|)
+ colsToBe += col.size
+ end
+ assert_equal(colsToBe, cols)
+ assert_equal(toBe, buf)
end
def test_s_parse_line
@@ -901,6 +959,16 @@ public
assert_equal(cols, buf.size, "Reported size.")
assert_equal(col.size, buf.size, "Size.")
assert(buf.match(col))
+
+ # separator: |
+ buf = CSV::Row.new
+ cols, idx = CSV.parse_row(str + "|", 0, buf, ?,)
+ assert(!buf.match(col))
+ buf = CSV::Row.new
+ cols, idx = CSV.parse_row(str + "|", 0, buf, ?,, ?|)
+ assert_equal(cols, buf.size, "Reported size.")
+ assert_equal(col.size, buf.size, "Size.")
+ assert(buf.match(col))
end
@@fullCSVData.each do |col, str|
@@ -921,6 +989,15 @@ public
assert(buf.match(col))
end
+ @@fullCSVData.each do |col, str|
+ str = csv2tsv(str, ?|)
+ buf = CSV::Row.new
+ cols, idx = CSV.parse_row(str + "|", 0, buf, ?\t, ?|)
+ assert_equal(cols, buf.size, "Reported size.")
+ assert_equal(col.size, buf.size, "Size.")
+ assert(buf.match(col), str)
+ end
+
buf = CSV::Row.new
cols, idx = CSV.parse_row("a,b,\"c\r\"", 0, buf)
assert_equal(["a", "b", "c\r"], buf.to_a)
@@ -1086,6 +1163,24 @@ public
assert_equal(toBe.size, parsedCols)
assert_equal(toBe.size, parsed.size)
assert(parsed.match(toBe))
+
+ buf = ''
+ toBe = []
+ @@fullCSVData.each do |col, str|
+ buf << str << "|"
+ toBe.concat(col)
+ end
+ idx = 0
+ cols = 0
+ parsed = CSV::Row.new
+ parsedCols = 0
+ begin
+ cols, idx = CSV.parse_row(buf, idx, parsed, ?,, ?|)
+ parsedCols += cols
+ end while cols > 0
+ assert_equal(toBe.size, parsedCols)
+ assert_equal(toBe.size, parsed.size)
+ assert(parsed.match(toBe))
end
def test_utf8
@@ -1104,6 +1199,22 @@ public
file.close
end
+ def test_macCR
+ rows = []
+ CSV.open(@macfile, "r", ?,, ?\r) do |row|
+ rows << row.to_a
+ end
+ assert_equal([["Avenches", "aus Umgebung"], ["Bad Hersfeld", "Ausgrabung"]], rows)
+
+ rows = []
+ file = File.open(@macfile)
+ CSV::Reader.parse(file.read, ?,, ?\r) do |row|
+ rows << row.to_a
+ end
+ assert_equal([["Avenches", "aus Umgebung"], ["Bad Hersfeld", "Ausgrabung"]], rows)
+ file.close
+ end
+
#### CSV unit test
@@ -1518,3 +1629,13 @@ public
assert_equal(csvStrTerminated, buf)
end
end
+
+
+if $0 == __FILE__
+ suite = Test::Unit::TestSuite.new('CSV')
+ ObjectSpace.each_object(Class) do |klass|
+ suite << klass.suite if (Test::Unit::TestCase > klass)
+ end
+ require 'test/unit/ui/console/testrunner'
+ Test::Unit::UI::Console::TestRunner.run(suite).passed?
+end