csv 3.1.4 → 3.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +97 -0
  3. data/README.md +5 -0
  4. data/doc/csv/arguments/io.rdoc +5 -0
  5. data/doc/csv/options/common/col_sep.rdoc +57 -0
  6. data/doc/csv/options/common/quote_char.rdoc +42 -0
  7. data/doc/csv/options/common/row_sep.rdoc +91 -0
  8. data/doc/csv/options/generating/force_quotes.rdoc +17 -0
  9. data/doc/csv/options/generating/quote_empty.rdoc +12 -0
  10. data/doc/csv/options/generating/write_converters.rdoc +25 -0
  11. data/doc/csv/options/generating/write_empty_value.rdoc +15 -0
  12. data/doc/csv/options/generating/write_headers.rdoc +29 -0
  13. data/doc/csv/options/generating/write_nil_value.rdoc +14 -0
  14. data/doc/csv/options/parsing/converters.rdoc +46 -0
  15. data/doc/csv/options/parsing/empty_value.rdoc +13 -0
  16. data/doc/csv/options/parsing/field_size_limit.rdoc +39 -0
  17. data/doc/csv/options/parsing/header_converters.rdoc +43 -0
  18. data/doc/csv/options/parsing/headers.rdoc +63 -0
  19. data/doc/csv/options/parsing/liberal_parsing.rdoc +19 -0
  20. data/doc/csv/options/parsing/nil_value.rdoc +12 -0
  21. data/doc/csv/options/parsing/return_headers.rdoc +22 -0
  22. data/doc/csv/options/parsing/skip_blanks.rdoc +31 -0
  23. data/doc/csv/options/parsing/skip_lines.rdoc +37 -0
  24. data/doc/csv/options/parsing/strip.rdoc +15 -0
  25. data/doc/csv/options/parsing/unconverted_fields.rdoc +27 -0
  26. data/doc/csv/recipes/filtering.rdoc +158 -0
  27. data/doc/csv/recipes/generating.rdoc +298 -0
  28. data/doc/csv/recipes/parsing.rdoc +545 -0
  29. data/doc/csv/recipes/recipes.rdoc +6 -0
  30. data/lib/csv.rb +1570 -520
  31. data/lib/csv/parser.rb +1 -0
  32. data/lib/csv/row.rb +478 -133
  33. data/lib/csv/table.rb +750 -108
  34. data/lib/csv/version.rb +1 -1
  35. data/lib/csv/writer.rb +45 -4
  36. metadata +42 -6
@@ -0,0 +1,298 @@
1
+ == Recipes for Generating \CSV
2
+
3
+ These recipes are specific code examples for specific \CSV generating tasks.
4
+
5
+ For other recipes, see {Recipes for CSV}[./recipes_rdoc.html].
6
+
7
+ All code snippets on this page assume that the following has been executed:
8
+ require 'csv'
9
+
10
+ === Contents
11
+
12
+ - {Output Formats}[#label-Output+Formats]
13
+ - {Generating to a String}[#label-Generating+to+a+String]
14
+ - {Recipe: Generate to String with Headers}[#label-Recipe-3A+Generate+to+String+with+Headers]
15
+ - {Recipe: Generate to String Without Headers}[#label-Recipe-3A+Generate+to+String+Without+Headers]
16
+ - {Generating to a File}[#label-Generating+to+a+File]
17
+ - {Recipe: Generate to File with Headers}[#label-Recipe-3A+Generate+to+File+with+Headers]
18
+ - {Recipe: Generate to File Without Headers}[#label-Recipe-3A+Generate+to+File+Without+Headers]
19
+ - {Generating to IO an Stream}[#label-Generating+to+an+IO+Stream]
20
+ - {Recipe: Generate to IO Stream with Headers}[#label-Recipe-3A+Generate+to+IO+Stream+with+Headers]
21
+ - {Recipe: Generate to IO Stream Without Headers}[#label-Recipe-3A+Generate+to+IO+Stream+Without+Headers]
22
+ - {Converting Fields}[#label-Converting+Fields]
23
+ - {Recipe: Filter Generated Field Strings}[#label-Recipe-3A+Filter+Generated+Field+Strings]
24
+ - {Recipe: Specify Multiple Write Converters}[#label-Recipe-3A+Specify+Multiple+Write+Converters]
25
+ - {RFC 4180 Compliance}[#label-RFC+4180+Compliance]
26
+ - {Row Separator}[#label-Row+Separator]
27
+ - {Recipe: Generate Compliant Row Separator}[#label-Recipe-3A+Generate+Compliant+Row+Separator]
28
+ - {Recipe: Generate Non-Compliant Row Separator}[#label-Recipe-3A+Generate+Non-Compliant+Row+Separator]
29
+ - {Column Separator}[#label-Column+Separator]
30
+ - {Recipe: Generate Compliant Column Separator}[#label-Recipe-3A+Generate+Compliant+Column+Separator]
31
+ - {Recipe: Generate Non-Compliant Column Separator}[#label-Recipe-3A+Generate+Non-Compliant+Column+Separator]
32
+ - {Quotes}[#label-Quotes]
33
+ - {Recipe: Quote All Fields}[#label-Recipe-3A+Quote+All+Fields]
34
+ - {Recipe: Quote Empty Fields}[#label-Recipe-3A+Quote+Empty+Fields]
35
+ - {Recipe: Generate Compliant Quote Character}[#label-Recipe-3A+Generate+Compliant+Quote+Character]
36
+ - {Recipe: Generate Non-Compliant Quote Character}[#label-Recipe-3A+Generate+Non-Compliant+Quote+Character]
37
+
38
+ === Output Formats
39
+
40
+ You can generate \CSV output to a \String, to a \File (via its path), or to an \IO stream.
41
+
42
+ ==== Generating to a \String
43
+
44
+ You can generate \CSV output to a \String, with or without headers.
45
+
46
+ ===== Recipe: Generate to \String with Headers
47
+
48
+ Use class method CSV.generate with option +headers+ to generate to a \String.
49
+
50
+ This example uses method CSV#<< to append the rows
51
+ that are to be generated:
52
+ output_string = CSV.generate('', headers: ['Name', 'Value'], write_headers: true) do |csv|
53
+ csv << ['Foo', 0]
54
+ csv << ['Bar', 1]
55
+ csv << ['Baz', 2]
56
+ end
57
+ output_string # => "Name,Value\nFoo,0\nBar,1\nBaz,2\n"
58
+
59
+ ===== Recipe: Generate to \String Without Headers
60
+
61
+ Use class method CSV.generate without option +headers+ to generate to a \String.
62
+
63
+ This example uses method CSV#<< to append the rows
64
+ that are to be generated:
65
+ output_string = CSV.generate do |csv|
66
+ csv << ['Foo', 0]
67
+ csv << ['Bar', 1]
68
+ csv << ['Baz', 2]
69
+ end
70
+ output_string # => "Foo,0\nBar,1\nBaz,2\n"
71
+
72
+ ==== Generating to a \File
73
+
74
+ You can generate /CSV data to a \File, with or without headers.
75
+
76
+ ===== Recipe: Generate to \File with Headers
77
+
78
+ Use class method CSV.open with option +headers+ generate to a \File.
79
+
80
+ This example uses method CSV#<< to append the rows
81
+ that are to be generated:
82
+ path = 't.csv'
83
+ CSV.open(path, 'w', headers: ['Name', 'Value'], write_headers: true) do |csv|
84
+ csv << ['Foo', 0]
85
+ csv << ['Bar', 1]
86
+ csv << ['Baz', 2]
87
+ end
88
+ p File.read(path) # => "Name,Value\nFoo,0\nBar,1\nBaz,2\n"
89
+
90
+ ===== Recipe: Generate to \File Without Headers
91
+
92
+ Use class method CSV.open without option +headers+ to generate to a \File.
93
+
94
+ This example uses method CSV#<< to append the rows
95
+ that are to be generated:
96
+ path = 't.csv'
97
+ CSV.open(path, 'w') do |csv|
98
+ csv << ['Foo', 0]
99
+ csv << ['Bar', 1]
100
+ csv << ['Baz', 2]
101
+ end
102
+ p File.read(path) # => "Foo,0\nBar,1\nBaz,2\n"
103
+
104
+ ==== Generating to an \IO Stream
105
+
106
+ You can generate \CSV data to an \IO stream, with or without headers.
107
+
108
+ ==== Recipe: Generate to \IO Stream with Headers
109
+
110
+ Use class method CSV.new with option +headers+ to generate \CSV data to an \IO stream:
111
+ path = 't.csv'
112
+ File.open(path, 'w') do |file|
113
+ csv = CSV.new(file, headers: ['Name', 'Value'], write_headers: true)
114
+ csv << ['Foo', 0]
115
+ csv << ['Bar', 1]
116
+ csv << ['Baz', 2]
117
+ end
118
+ p File.read(path) # => "Name,Value\nFoo,0\nBar,1\nBaz,2\n"
119
+
120
+ ===== Recipe: Generate to \IO Stream Without Headers
121
+
122
+ Use class method CSV.new without option +headers+ to generate \CSV data to an \IO stream:
123
+ path = 't.csv'
124
+ File.open(path, 'w') do |file|
125
+ csv = CSV.new(file)
126
+ csv << ['Foo', 0]
127
+ csv << ['Bar', 1]
128
+ csv << ['Baz', 2]
129
+ end
130
+ p File.read(path) # => "Foo,0\nBar,1\nBaz,2\n"
131
+
132
+ === Converting Fields
133
+
134
+ You can use _write_ _converters_ to convert fields when generating \CSV.
135
+
136
+ ==== Recipe: Filter Generated Field Strings
137
+
138
+ Use option <tt>:write_converters</tt> and a custom converter to convert field values when generating \CSV.
139
+
140
+ This example defines and uses a custom write converter to strip whitespace from generated fields:
141
+ strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
142
+ output_string = CSV.generate(write_converters: strip_converter) do |csv|
143
+ csv << [' foo ', 0]
144
+ csv << [' bar ', 1]
145
+ csv << [' baz ', 2]
146
+ end
147
+ output_string # => "foo,0\nbar,1\nbaz,2\n"
148
+
149
+ ==== Recipe: Specify Multiple Write Converters
150
+
151
+ Use option <tt>:write_converters</tt> and multiple custom coverters
152
+ to convert field values when generating \CSV.
153
+
154
+ This example defines and uses two custom write converters to strip and upcase generated fields:
155
+ strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
156
+ upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
157
+ converters = [strip_converter, upcase_converter]
158
+ output_string = CSV.generate(write_converters: converters) do |csv|
159
+ csv << [' foo ', 0]
160
+ csv << [' bar ', 1]
161
+ csv << [' baz ', 2]
162
+ end
163
+ output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
164
+
165
+ === RFC 4180 Compliance
166
+
167
+ By default, \CSV generates data that is compliant with
168
+ {RFC 4180}[https://tools.ietf.org/html/rfc4180]
169
+ with respect to:
170
+ - Column separator.
171
+ - Quote character.
172
+
173
+ ==== Row Separator
174
+
175
+ RFC 4180 specifies the row separator CRLF (Ruby <tt>"\r\n"</tt>).
176
+
177
+ ===== Recipe: Generate Compliant Row Separator
178
+
179
+ For strict compliance, use option +:row_sep+ to specify row separator <tt>"\r\n"</tt>:
180
+ output_string = CSV.generate('', row_sep: "\r\n") do |csv|
181
+ csv << ['Foo', 0]
182
+ csv << ['Bar', 1]
183
+ csv << ['Baz', 2]
184
+ end
185
+ output_string # => "Foo,0\r\nBar,1\r\nBaz,2\r\n"
186
+
187
+ ===== Recipe: Generate Non-Compliant Row Separator
188
+
189
+ For data with non-compliant row separators, use option +:row_sep+ with a different value:
190
+ This example source uses semicolon (<tt>";'</tt>) as its row separator:
191
+ output_string = CSV.generate('', row_sep: ";") do |csv|
192
+ csv << ['Foo', 0]
193
+ csv << ['Bar', 1]
194
+ csv << ['Baz', 2]
195
+ end
196
+ output_string # => "Foo,0;Bar,1;Baz,2;"
197
+
198
+ ==== Column Separator
199
+
200
+ RFC 4180 specifies column separator COMMA (Ruby <tt>","</tt>).
201
+
202
+ ===== Recipe: Generate Compliant Column Separator
203
+
204
+ Because the \CSV default comma separator is <tt>","</tt>,
205
+ you need not specify option +:col_sep+ for compliant data:
206
+ output_string = CSV.generate('') do |csv|
207
+ csv << ['Foo', 0]
208
+ csv << ['Bar', 1]
209
+ csv << ['Baz', 2]
210
+ end
211
+ output_string # => "Foo,0\nBar,1\nBaz,2\n"
212
+
213
+ ===== Recipe: Generate Non-Compliant Column Separator
214
+
215
+ For data with non-compliant column separators, use option +:col_sep+.
216
+ This example source uses TAB (<tt>"\t"</tt>) as its column separator:
217
+ output_string = CSV.generate('', col_sep: "\t") do |csv|
218
+ csv << ['Foo', 0]
219
+ csv << ['Bar', 1]
220
+ csv << ['Baz', 2]
221
+ end
222
+ output_string # => "Foo\t0\nBar\t1\nBaz\t2\n"
223
+
224
+ ==== Quotes
225
+
226
+ IFC 4180 allows most fields to be quoted or not.
227
+ By default, \CSV does not quote most fields.
228
+
229
+ However, a field containing the current row separator, column separator,
230
+ or quote character is automatically quoted, producing IFC 4180 compliance:
231
+ # Field contains row separator.
232
+ output_string = CSV.generate('') do |csv|
233
+ row_sep = csv.row_sep
234
+ csv << ["Foo#{row_sep}Foo", 0]
235
+ csv << ['Bar', 1]
236
+ csv << ['Baz', 2]
237
+ end
238
+ output_string # => "\"Foo\nFoo\",0\nBar,1\nBaz,2\n"
239
+ # Field contains column separator.
240
+ output_string = CSV.generate('') do |csv|
241
+ col_sep = csv.col_sep
242
+ csv << ["Foo#{col_sep}Foo", 0]
243
+ csv << ['Bar', 1]
244
+ csv << ['Baz', 2]
245
+ end
246
+ output_string # => "\"Foo,Foo\",0\nBar,1\nBaz,2\n"
247
+ # Field contains quote character.
248
+ output_string = CSV.generate('') do |csv|
249
+ quote_char = csv.quote_char
250
+ csv << ["Foo#{quote_char}Foo", 0]
251
+ csv << ['Bar', 1]
252
+ csv << ['Baz', 2]
253
+ end
254
+ output_string # => "\"Foo\"\"Foo\",0\nBar,1\nBaz,2\n"
255
+
256
+ ===== Recipe: Quote All Fields
257
+
258
+ Use option +:force_quotes+ to force quoted fields:
259
+ output_string = CSV.generate('', force_quotes: true) do |csv|
260
+ csv << ['Foo', 0]
261
+ csv << ['Bar', 1]
262
+ csv << ['Baz', 2]
263
+ end
264
+ output_string # => "\"Foo\",\"0\"\n\"Bar\",\"1\"\n\"Baz\",\"2\"\n"
265
+
266
+ ===== Recipe: Quote Empty Fields
267
+
268
+ Use option +:quote_empty+ to force quoting for empty fields:
269
+ output_string = CSV.generate('', quote_empty: true) do |csv|
270
+ csv << ['Foo', 0]
271
+ csv << ['Bar', 1]
272
+ csv << ['', 2]
273
+ end
274
+ output_string # => "Foo,0\nBar,1\n\"\",2\n"
275
+
276
+ ===== Recipe: Generate Compliant Quote Character
277
+
278
+ RFC 4180 specifies quote character DQUOTE (Ruby <tt>"\""</tt>).
279
+
280
+ Because the \CSV default quote character is also <tt>"\""</tt>,
281
+ you need not specify option +:quote_char+ for compliant data:
282
+ output_string = CSV.generate('', force_quotes: true) do |csv|
283
+ csv << ['Foo', 0]
284
+ csv << ['Bar', 1]
285
+ csv << ['Baz', 2]
286
+ end
287
+ output_string # => "\"Foo\",\"0\"\n\"Bar\",\"1\"\n\"Baz\",\"2\"\n"
288
+
289
+ ===== Recipe: Generate Non-Compliant Quote Character
290
+
291
+ For data with non-compliant quote characters, use option +:quote_char+.
292
+ This example source uses SQUOTE (<tt>"'"</tt>) as its quote character:
293
+ output_string = CSV.generate('', quote_char: "'", force_quotes: true) do |csv|
294
+ csv << ['Foo', 0]
295
+ csv << ['Bar', 1]
296
+ csv << ['Baz', 2]
297
+ end
298
+ output_string # => "'Foo','0'\n'Bar','1'\n'Baz','2'\n"
@@ -0,0 +1,545 @@
1
+ == Recipes for Parsing \CSV
2
+
3
+ These recipes are specific code examples for specific \CSV parsing tasks.
4
+
5
+ For other recipes, see {Recipes for CSV}[./recipes_rdoc.html].
6
+
7
+ All code snippets on this page assume that the following has been executed:
8
+ require 'csv'
9
+
10
+ === Contents
11
+
12
+ - {Source Formats}[#label-Source+Formats]
13
+ - {Parsing from a String}[#label-Parsing+from+a+String]
14
+ - {Recipe: Parse from String with Headers}[#label-Recipe-3A+Parse+from+String+with+Headers]
15
+ - {Recipe: Parse from String Without Headers}[#label-Recipe-3A+Parse+from+String+Without+Headers]
16
+ - {Parsing from a File}[#label-Parsing+from+a+File]
17
+ - {Recipe: Parse from File with Headers}[#label-Recipe-3A+Parse+from+File+with+Headers]
18
+ - {Recipe: Parse from File Without Headers}[#label-Recipe-3A+Parse+from+File+Without+Headers]
19
+ - {Parsing from an IO Stream}[#label-Parsing+from+an+IO+Stream]
20
+ - {Recipe: Parse from IO Stream with Headers}[#label-Recipe-3A+Parse+from+IO+Stream+with+Headers]
21
+ - {Recipe: Parse from IO Stream Without Headers}[#label-Recipe-3A+Parse+from+IO+Stream+Without+Headers]
22
+ - {RFC 4180 Compliance}[#label-RFC+4180+Compliance]
23
+ - {Row Separator}[#label-Row+Separator]
24
+ - {Recipe: Handle Compliant Row Separator}[#label-Recipe-3A+Handle+Compliant+Row+Separator]
25
+ - {Recipe: Handle Non-Compliant Row Separator}[#label-Recipe-3A+Handle+Non-Compliant+Row+Separator]
26
+ - {Column Separator}[#label-Column+Separator]
27
+ - {Recipe: Handle Compliant Column Separator}[#label-Recipe-3A+Handle+Compliant+Column+Separator]
28
+ - {Recipe: Handle Non-Compliant Column Separator}[#label-Recipe-3A+Handle+Non-Compliant+Column+Separator]
29
+ - {Quote Character}[#label-Quote+Character]
30
+ - {Recipe: Handle Compliant Quote Character}[#label-Recipe-3A+Handle+Compliant+Quote+Character]
31
+ - {Recipe: Handle Non-Compliant Quote Character}[#label-Recipe-3A+Handle+Non-Compliant+Quote+Character]
32
+ - {Recipe: Allow Liberal Parsing}[#label-Recipe-3A+Allow+Liberal+Parsing]
33
+ - {Special Handling}[#label-Special+Handling]
34
+ - {Special Line Handling}[#label-Special+Line+Handling]
35
+ - {Recipe: Ignore Blank Lines}[#label-Recipe-3A+Ignore+Blank+Lines]
36
+ - {Recipe: Ignore Selected Lines}[#label-Recipe-3A+Ignore+Selected+Lines]
37
+ - {Special Field Handling}[#label-Special+Field+Handling]
38
+ - {Recipe: Strip Fields}[#label-Recipe-3A+Strip+Fields]
39
+ - {Recipe: Handle Null Fields}[#label-Recipe-3A+Handle+Null+Fields]
40
+ - {Recipe: Handle Empty Fields}[#label-Recipe-3A+Handle+Empty+Fields]
41
+ - {Converting Fields}[#label-Converting+Fields]
42
+ - {Converting Fields to Objects}[#label-Converting+Fields+to+Objects]
43
+ - {Recipe: Convert Fields to Integers}[#label-Recipe-3A+Convert+Fields+to+Integers]
44
+ - {Recipe: Convert Fields to Floats}[#label-Recipe-3A+Convert+Fields+to+Floats]
45
+ - {Recipe: Convert Fields to Numerics}[#label-Recipe-3A+Convert+Fields+to+Numerics]
46
+ - {Recipe: Convert Fields to Dates}[#label-Recipe-3A+Convert+Fields+to+Dates]
47
+ - {Recipe: Convert Fields to DateTimes}[#label-Recipe-3A+Convert+Fields+to+DateTimes]
48
+ - {Recipe: Convert Assorted Fields to Objects}[#label-Recipe-3A+Convert+Assorted+Fields+to+Objects]
49
+ - {Recipe: Convert Fields to Other Objects}[#label-Recipe-3A+Convert+Fields+to+Other+Objects]
50
+ - {Recipe: Filter Field Strings}[#label-Recipe-3A+Filter+Field+Strings]
51
+ - {Recipe: Register Field Converters}[#label-Recipe-3A+Register+Field+Converters]
52
+ - {Using Multiple Field Converters}[#label-Using+Multiple+Field+Converters]
53
+ - {Recipe: Specify Multiple Field Converters in Option :converters}[#label-Recipe-3A+Specify+Multiple+Field+Converters+in+Option+-3Aconverters]
54
+ - {Recipe: Specify Multiple Field Converters in a Custom Converter List}[#label-Recipe-3A+Specify+Multiple+Field+Converters+in+a+Custom+Converter+List]
55
+ - {Converting Headers}[#label-Converting+Headers]
56
+ - {Recipe: Convert Headers to Lowercase}[#label-Recipe-3A+Convert+Headers+to+Lowercase]
57
+ - {Recipe: Convert Headers to Symbols}[#label-Recipe-3A+Convert+Headers+to+Symbols]
58
+ - {Recipe: Filter Header Strings}[#label-Recipe-3A+Filter+Header+Strings]
59
+ - {Recipe: Register Header Converters}[#label-Recipe-3A+Register+Header+Converters]
60
+ - {Using Multiple Header Converters}[#label-Using+Multiple+Header+Converters]
61
+ - {Recipe: Specify Multiple Header Converters in Option :header_converters}[#label-Recipe-3A+Specify+Multiple+Header+Converters+in+Option+-3Aheader_converters]
62
+ - {Recipe: Specify Multiple Header Converters in a Custom Header Converter List}[#label-Recipe-3A+Specify+Multiple+Header+Converters+in+a+Custom+Header+Converter+List]
63
+ - {Diagnostics}[#label-Diagnostics]
64
+ - {Recipe: Capture Unconverted Fields}[#label-Recipe-3A+Capture+Unconverted+Fields]
65
+ - {Recipe: Capture Field Info}[#label-Recipe-3A+Capture+Field+Info]
66
+
67
+ === Source Formats
68
+
69
+ You can parse \CSV data from a \String, from a \File (via its path), or from an \IO stream.
70
+
71
+ ==== Parsing from a \String
72
+
73
+ You can parse \CSV data from a \String, with or without headers.
74
+
75
+ ===== Recipe: Parse from \String with Headers
76
+
77
+ Use class method CSV.parse with option +headers+ to read a source \String all at once
78
+ (may have memory resource implications):
79
+ string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
80
+ CSV.parse(string, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
81
+
82
+ Use instance method CSV#each with option +headers+ to read a source \String one row at a time:
83
+ CSV.new(string, headers: true).each do |row|
84
+ p row
85
+ end
86
+ Ouput:
87
+ #<CSV::Row "Name":"foo" "Value":"0">
88
+ #<CSV::Row "Name":"bar" "Value":"1">
89
+ #<CSV::Row "Name":"baz" "Value":"2">
90
+
91
+ ===== Recipe: Parse from \String Without Headers
92
+
93
+ Use class method CSV.parse without option +headers+ to read a source \String all at once
94
+ (may have memory resource implications):
95
+ string = "foo,0\nbar,1\nbaz,2\n"
96
+ CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
97
+
98
+ Use instance method CSV#each without option +headers+ to read a source \String one row at a time:
99
+ CSV.new(string).each do |row|
100
+ p row
101
+ end
102
+ Output:
103
+ ["foo", "0"]
104
+ ["bar", "1"]
105
+ ["baz", "2"]
106
+
107
+ ==== Parsing from a \File
108
+
109
+ You can parse \CSV data from a \File, with or without headers.
110
+
111
+ ===== Recipe: Parse from \File with Headers
112
+
113
+ Use instance method CSV#read with option +headers+ to read a file all at once:
114
+ string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
115
+ path = 't.csv'
116
+ File.write(path, string)
117
+ CSV.read(path, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
118
+
119
+ Use class method CSV.foreach with option +headers+ to read one row at a time:
120
+ CSV.foreach(path, headers: true) do |row|
121
+ p row
122
+ end
123
+ Output:
124
+ #<CSV::Row "Name":"foo" "Value":"0">
125
+ #<CSV::Row "Name":"bar" "Value":"1">
126
+ #<CSV::Row "Name":"baz" "Value":"2">
127
+
128
+ ===== Recipe: Parse from \File Without Headers
129
+
130
+ Use class method CSV.read without option +headers+ to read a file all at once:
131
+ string = "foo,0\nbar,1\nbaz,2\n"
132
+ path = 't.csv'
133
+ File.write(path, string)
134
+ CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
135
+
136
+ Use class method CSV.foreach without option +headers+ to read one row at a time:
137
+ CSV.foreach(path) do |row|
138
+ p row
139
+ end
140
+ Output:
141
+ ["foo", "0"]
142
+ ["bar", "1"]
143
+ ["baz", "2"]
144
+
145
+ ==== Parsing from an \IO Stream
146
+
147
+ You can parse \CSV data from an \IO stream, with or without headers.
148
+
149
+ ===== Recipe: Parse from \IO Stream with Headers
150
+
151
+ Use class method CSV.parse with option +headers+ to read an \IO stream all at once:
152
+ string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
153
+ path = 't.csv'
154
+ File.write(path, string)
155
+ File.open(path) do |file|
156
+ CSV.parse(file, headers: true)
157
+ end # => #<CSV::Table mode:col_or_row row_count:4>
158
+
159
+ Use class method CSV.foreach with option +headers+ to read one row at a time:
160
+ File.open(path) do |file|
161
+ CSV.foreach(file, headers: true) do |row|
162
+ p row
163
+ end
164
+ end
165
+ Output:
166
+ #<CSV::Row "Name":"foo" "Value":"0">
167
+ #<CSV::Row "Name":"bar" "Value":"1">
168
+ #<CSV::Row "Name":"baz" "Value":"2">
169
+
170
+ ===== Recipe: Parse from \IO Stream Without Headers
171
+
172
+ Use class method CSV.parse without option +headers+ to read an \IO stream all at once:
173
+ string = "foo,0\nbar,1\nbaz,2\n"
174
+ path = 't.csv'
175
+ File.write(path, string)
176
+ File.open(path) do |file|
177
+ CSV.parse(file)
178
+ end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
179
+
180
+ Use class method CSV.foreach without option +headers+ to read one row at a time:
181
+ File.open(path) do |file|
182
+ CSV.foreach(file) do |row|
183
+ p row
184
+ end
185
+ end
186
+ Output:
187
+ ["foo", "0"]
188
+ ["bar", "1"]
189
+ ["baz", "2"]
190
+
191
+ === RFC 4180 Compliance
192
+
193
+ By default, \CSV parses data that is compliant with
194
+ {RFC 4180}[https://tools.ietf.org/html/rfc4180]
195
+ with respect to:
196
+ - Row separator.
197
+ - Column separator.
198
+ - Quote character.
199
+
200
+ ==== Row Separator
201
+
202
+ RFC 4180 specifies the row separator CRLF (Ruby <tt>"\r\n"</tt>).
203
+
204
+ Although the \CSV default row separator is <tt>"\n"</tt>,
205
+ the parser also by default handles row separator <tt>"\r"</tt> and the RFC-compliant <tt>"\r\n"</tt>.
206
+
207
+ ===== Recipe: Handle Compliant Row Separator
208
+
209
+ For strict compliance, use option +:row_sep+ to specify row separator <tt>"\r\n"</tt>,
210
+ which allows the compliant row separator:
211
+ source = "foo,1\r\nbar,1\r\nbaz,2\r\n"
212
+ CSV.parse(source, row_sep: "\r\n") # => [["foo", "1"], ["bar", "1"], ["baz", "2"]]
213
+ But rejects other row separators:
214
+ source = "foo,1\nbar,1\nbaz,2\n"
215
+ CSV.parse(source, row_sep: "\r\n") # Raised MalformedCSVError
216
+ source = "foo,1\rbar,1\rbaz,2\r"
217
+ CSV.parse(source, row_sep: "\r\n") # Raised MalformedCSVError
218
+ source = "foo,1\n\rbar,1\n\rbaz,2\n\r"
219
+ CSV.parse(source, row_sep: "\r\n") # Raised MalformedCSVError
220
+
221
+ ===== Recipe: Handle Non-Compliant Row Separator
222
+
223
+ For data with non-compliant row separators, use option +:row_sep+.
224
+ This example source uses semicolon (<tt>";"</tt>) as its row separator:
225
+ source = "foo,1;bar,1;baz,2;"
226
+ CSV.parse(source, row_sep: ';') # => [["foo", "1"], ["bar", "1"], ["baz", "2"]]
227
+
228
+ ==== Column Separator
229
+
230
+ RFC 4180 specifies column separator COMMA (Ruby <tt>","</tt>).
231
+
232
+ ===== Recipe: Handle Compliant Column Separator
233
+
234
+ Because the \CSV default comma separator is ',',
235
+ you need not specify option +:col_sep+ for compliant data:
236
+ source = "foo,1\nbar,1\nbaz,2\n"
237
+ CSV.parse(source) # => [["foo", "1"], ["bar", "1"], ["baz", "2"]]
238
+
239
+ ===== Recipe: Handle Non-Compliant Column Separator
240
+
241
+ For data with non-compliant column separators, use option +:col_sep+.
242
+ This example source uses TAB (<tt>"\t"</tt>) as its column separator:
243
+ source = "foo,1\tbar,1\tbaz,2"
244
+ CSV.parse(source, col_sep: "\t") # => [["foo", "1"], ["bar", "1"], ["baz", "2"]]
245
+
246
+ ==== Quote Character
247
+
248
+ RFC 4180 specifies quote character DQUOTE (Ruby <tt>"\""</tt>).
249
+
250
+ ===== Recipe: Handle Compliant Quote Character
251
+
252
+ Because the \CSV default quote character is <tt>"\""</tt>,
253
+ you need not specify option +:quote_char+ for compliant data:
254
+ source = "\"foo\",\"1\"\n\"bar\",\"1\"\n\"baz\",\"2\"\n"
255
+ CSV.parse(source) # => [["foo", "1"], ["bar", "1"], ["baz", "2"]]
256
+
257
+ ===== Recipe: Handle Non-Compliant Quote Character
258
+
259
+ For data with non-compliant quote characters, use option +:quote_char+.
260
+ This example source uses SQUOTE (<tt>"'"</tt>) as its quote character:
261
+ source = "'foo','1'\n'bar','1'\n'baz','2'\n"
262
+ CSV.parse(source, quote_char: "'") # => [["foo", "1"], ["bar", "1"], ["baz", "2"]]
263
+
264
+ ==== Recipe: Allow Liberal Parsing
265
+
266
+ Use option +:liberal_parsing+ to specify that \CSV should
267
+ attempt to parse input not conformant with RFC 4180, such as double quotes in unquoted fields:
268
+ source = 'is,this "three, or four",fields'
269
+ CSV.parse(source) # Raises MalformedCSVError
270
+ CSV.parse(source, liberal_parsing: true) # => [["is", "this \"three", " or four\"", "fields"]]
271
+
272
+ === Special Handling
273
+
274
+ You can use parsing options to specify special handling for certain lines and fields.
275
+
276
+ ==== Special Line Handling
277
+
278
+ Use parsing options to specify special handling for blank lines, or for other selected lines.
279
+
280
+ ===== Recipe: Ignore Blank Lines
281
+
282
+ Use option +:skip_blanks+ to ignore blank lines:
283
+ source = <<-EOT
284
+ foo,0
285
+
286
+ bar,1
287
+ baz,2
288
+
289
+ ,
290
+ EOT
291
+ parsed = CSV.parse(source, skip_blanks: true)
292
+ parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]
293
+
294
+ ===== Recipe: Ignore Selected Lines
295
+
296
+ Use option +:skip_lines+ to ignore selected lines.
297
+ source = <<-EOT
298
+ # Comment
299
+ foo,0
300
+ bar,1
301
+ baz,2
302
+ # Another comment
303
+ EOT
304
+ parsed = CSV.parse(source, skip_lines: /^#/)
305
+ parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
306
+
307
+ ==== Special Field Handling
308
+
309
+ Use parsing options to specify special handling for certain field values.
310
+
311
+ ===== Recipe: Strip Fields
312
+
313
+ Use option +:strip+ to strip parsed field values:
314
+ CSV.parse_line(' a , b ', strip: true) # => ["a", "b"]
315
+
316
+ ===== Recipe: Handle Null Fields
317
+
318
+ Use option +:nil_value+ to specify a value that will replace each field
319
+ that is null (no text):
320
+ CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"]
321
+
322
+ ===== Recipe: Handle Empty Fields
323
+
324
+ Use option +:empty_value+ to specify a value that will replace each field
325
+ that is empty (\String of length 0);
326
+ CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"]
327
+
328
+ === Converting Fields
329
+
330
+ You can use field converters to change parsed \String fields into other objects,
331
+ or to otherwise modify the \String fields.
332
+
333
+ ==== Converting Fields to Objects
334
+
335
+ Use field converters to change parsed \String objects into other, more specific, objects.
336
+
337
+ There are built-in field converters for converting to objects of certain classes:
338
+ - \Float
339
+ - \Integer
340
+ - \Date
341
+ - \DateTime
342
+
343
+ Other built-in field converters include:
344
+ - +:numeric+: converts to \Integer and \Float.
345
+ - +:all+: converts to \DateTime, \Integer, \Float.
346
+
347
+ You can also define field converters to convert to objects of other classes.
348
+
349
+ ===== Recipe: Convert Fields to Integers
350
+
351
+ Convert fields to \Integer objects using built-in converter +:integer+:
352
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
353
+ parsed = CSV.parse(source, headers: true, converters: :integer)
354
+ parsed.map {|row| row['Value'].class} # => [Integer, Integer, Integer]
355
+
356
+ ===== Recipe: Convert Fields to Floats
357
+
358
+ Convert fields to \Float objects using built-in converter +:float+:
359
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
360
+ parsed = CSV.parse(source, headers: true, converters: :float)
361
+ parsed.map {|row| row['Value'].class} # => [Float, Float, Float]
362
+
363
+ ===== Recipe: Convert Fields to Numerics
364
+
365
+ Convert fields to \Integer and \Float objects using built-in converter +:numeric+:
366
+ source = "Name,Value\nfoo,0\nbar,1.1\nbaz,2.2\n"
367
+ parsed = CSV.parse(source, headers: true, converters: :numeric)
368
+ parsed.map {|row| row['Value'].class} # => [Integer, Float, Float]
369
+
370
+ ===== Recipe: Convert Fields to Dates
371
+
372
+ Convert fields to \Date objects using built-in converter +:date+:
373
+ source = "Name,Date\nfoo,2001-02-03\nbar,2001-02-04\nbaz,2001-02-03\n"
374
+ parsed = CSV.parse(source, headers: true, converters: :date)
375
+ parsed.map {|row| row['Date'].class} # => [Date, Date, Date]
376
+
377
+ ===== Recipe: Convert Fields to DateTimes
378
+
379
+ Convert fields to \DateTime objects using built-in converter +:date_time+:
380
+ source = "Name,DateTime\nfoo,2001-02-03\nbar,2001-02-04\nbaz,2020-05-07T14:59:00-05:00\n"
381
+ parsed = CSV.parse(source, headers: true, converters: :date_time)
382
+ parsed.map {|row| row['DateTime'].class} # => [DateTime, DateTime, DateTime]
383
+
384
+ ===== Recipe: Convert Assorted Fields to Objects
385
+
386
+ Convert assorted fields to objects using built-in converter +:all+:
387
+ source = "Type,Value\nInteger,0\nFloat,1.0\nDateTime,2001-02-04\n"
388
+ parsed = CSV.parse(source, headers: true, converters: :all)
389
+ parsed.map {|row| row['Value'].class} # => [Integer, Float, DateTime]
390
+
391
+ ===== Recipe: Convert Fields to Other Objects
392
+
393
+ Define a custom field converter to convert \String fields into other objects.
394
+ This example defines and uses a custom field converter
395
+ that converts each column-1 value to a \Rational object:
396
+ rational_converter = proc do |field, field_context|
397
+ field_context.index == 1 ? field.to_r : field
398
+ end
399
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
400
+ parsed = CSV.parse(source, headers: true, converters: rational_converter)
401
+ parsed.map {|row| row['Value'].class} # => [Rational, Rational, Rational]
402
+
403
+ ==== Recipe: Filter Field Strings
404
+
405
+ Define a custom field converter to modify \String fields.
406
+ This example defines and uses a custom field converter
407
+ that strips whitespace from each field value:
408
+ strip_converter = proc {|field| field.strip }
409
+ source = "Name,Value\n foo , 0 \n bar , 1 \n baz , 2 \n"
410
+ parsed = CSV.parse(source, headers: true, converters: strip_converter)
411
+ parsed['Name'] # => ["foo", "bar", "baz"]
412
+ parsed['Value'] # => ["0", "1", "2"]
413
+
414
+ ==== Recipe: Register Field Converters
415
+
416
+ Register a custom field converter, assigning it a name;
417
+ then refer to the converter by its name:
418
+ rational_converter = proc do |field, field_context|
419
+ field_context.index == 1 ? field.to_r : field
420
+ end
421
+ CSV::Converters[:rational] = rational_converter
422
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
423
+ parsed = CSV.parse(source, headers: true, converters: :rational)
424
+ parsed['Value'] # => [(0/1), (1/1), (2/1)]
425
+
426
+ ==== Using Multiple Field Converters
427
+
428
+ You can use multiple field converters in either of these ways:
429
+ - Specify converters in option +:converters+.
430
+ - Specify converters in a custom converter list.
431
+
432
+ ===== Recipe: Specify Multiple Field Converters in Option +:converters+
433
+
434
+ Apply multiple field converters by specifying them in option +:conveters+:
435
+ source = "Name,Value\nfoo,0\nbar,1.0\nbaz,2.0\n"
436
+ parsed = CSV.parse(source, headers: true, converters: [:integer, :float])
437
+ parsed['Value'] # => [0, 1.0, 2.0]
438
+
439
+ ===== Recipe: Specify Multiple Field Converters in a Custom Converter List
440
+
441
+ Apply multiple field converters by defining and registering a custom converter list:
442
+ strip_converter = proc {|field| field.strip }
443
+ CSV::Converters[:strip] = strip_converter
444
+ CSV::Converters[:my_converters] = [:integer, :float, :strip]
445
+ source = "Name,Value\n foo , 0 \n bar , 1.0 \n baz , 2.0 \n"
446
+ parsed = CSV.parse(source, headers: true, converters: :my_converters)
447
+ parsed['Name'] # => ["foo", "bar", "baz"]
448
+ parsed['Value'] # => [0, 1.0, 2.0]
449
+
450
+ === Converting Headers
451
+
452
+ You can use header converters to modify parsed \String headers.
453
+
454
+ Built-in header converters include:
455
+ - +:symbol+: converts \String header to \Symbol.
456
+ - +:downcase+: converts \String header to lowercase.
457
+
458
+ You can also define header converters to otherwise modify header \Strings.
459
+
460
+ ==== Recipe: Convert Headers to Lowercase
461
+
462
+ Convert headers to lowercase using built-in converter +:downcase+:
463
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
464
+ parsed = CSV.parse(source, headers: true, header_converters: :downcase)
465
+ parsed.headers # => ["name", "value"]
466
+
467
+ ==== Recipe: Convert Headers to Symbols
468
+
469
+ Convert headers to downcased Symbols using built-in converter +:symbol+:
470
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
471
+ parsed = CSV.parse(source, headers: true, header_converters: :symbol)
472
+ parsed.headers # => [:name, :value]
473
+ parsed.headers.map {|header| header.class} # => [Symbol, Symbol]
474
+
475
+ ==== Recipe: Filter Header Strings
476
+
477
+ Define a custom header converter to modify \String fields.
478
+ This example defines and uses a custom header converter
479
+ that capitalizes each header \String:
480
+ capitalize_converter = proc {|header| header.capitalize }
481
+ source = "NAME,VALUE\nfoo,0\nbar,1\nbaz,2\n"
482
+ parsed = CSV.parse(source, headers: true, header_converters: capitalize_converter)
483
+ parsed.headers # => ["Name", "Value"]
484
+
485
+ ==== Recipe: Register Header Converters
486
+
487
+ Register a custom header converter, assigning it a name;
488
+ then refer to the converter by its name:
489
+ capitalize_converter = proc {|header| header.capitalize }
490
+ CSV::HeaderConverters[:capitalize] = capitalize_converter
491
+ source = "NAME,VALUE\nfoo,0\nbar,1\nbaz,2\n"
492
+ parsed = CSV.parse(source, headers: true, header_converters: :capitalize)
493
+ parsed.headers # => ["Name", "Value"]
494
+
495
+ ==== Using Multiple Header Converters
496
+
497
+ You can use multiple header converters in either of these ways:
498
+ - Specify header converters in option +:header_converters+.
499
+ - Specify header converters in a custom header converter list.
500
+
501
+ ===== Recipe: Specify Multiple Header Converters in Option :header_converters
502
+
503
+ Apply multiple header converters by specifying them in option +:header_conveters+:
504
+ source = "Name,Value\nfoo,0\nbar,1.0\nbaz,2.0\n"
505
+ parsed = CSV.parse(source, headers: true, header_converters: [:downcase, :symbol])
506
+ parsed.headers # => [:name, :value]
507
+
508
+ ===== Recipe: Specify Multiple Header Converters in a Custom Header Converter List
509
+
510
+ Apply multiple header converters by defining and registering a custom header converter list:
511
+ CSV::HeaderConverters[:my_header_converters] = [:symbol, :downcase]
512
+ source = "NAME,VALUE\nfoo,0\nbar,1.0\nbaz,2.0\n"
513
+ parsed = CSV.parse(source, headers: true, header_converters: :my_header_converters)
514
+ parsed.headers # => [:name, :value]
515
+
516
+ === Diagnostics
517
+
518
+ ==== Recipe: Capture Unconverted Fields
519
+
520
+ To capture unconverted field values, use option +:unconverted_fields+:
521
+ source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
522
+ parsed = CSV.parse(source, converters: :integer, unconverted_fields: true)
523
+ parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
524
+ parsed.each {|row| p row.unconverted_fields }
525
+ Output:
526
+ ["Name", "Value"]
527
+ ["foo", "0"]
528
+ ["bar", "1"]
529
+ ["baz", "2"]
530
+
531
+ ==== Recipe: Capture Field Info
532
+
533
+ To capture field info in a custom converter, accept two block arguments.
534
+ The first is the field value; the second is a +CSV::FieldInfo+ object:
535
+ strip_converter = proc {|field, field_info| p field_info; field.strip }
536
+ source = " foo , 0 \n bar , 1 \n baz , 2 \n"
537
+ parsed = CSV.parse(source, converters: strip_converter)
538
+ parsed # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
539
+ Output:
540
+ #<struct CSV::FieldInfo index=0, line=1, header=nil>
541
+ #<struct CSV::FieldInfo index=1, line=1, header=nil>
542
+ #<struct CSV::FieldInfo index=0, line=2, header=nil>
543
+ #<struct CSV::FieldInfo index=1, line=2, header=nil>
544
+ #<struct CSV::FieldInfo index=0, line=3, header=nil>
545
+ #<struct CSV::FieldInfo index=1, line=3, header=nil>