csv 3.1.7 → 3.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>