类 CSV
CSV¶ ↑
CSV Data
¶ ↑
CSV(逗号分隔值)数据是表格的文本表示
-
行分隔符分隔表格行。常见的行分隔符是换行符
"\n"
。 -
列分隔符分隔行中的字段。常见的列分隔符是逗号
","
。
此 CSV 字符串的行分隔符为"\n"
,列分隔符为","
,有 3 行和 2 列
"foo,0\nbar,1\nbaz,2\n"
尽管名称为 CSV,但 CSV 表示可以使用不同的分隔符。
有关表格的更多信息,请参阅维基百科文章“表(信息)”,特别是其“简单表”部分
类 CSV¶ ↑
类
CSV 提供用于以下操作的方法
-
从字符串对象、文件(通过其文件路径)或 IO 对象解析 CSV 数据。
-
生成 CSV 数据到字符串对象。
使 CSV 可用
require 'csv'
此处的所有示例均假设已完成此操作。
保持简单¶ ↑
CSV 对象有几十个实例方法,可对解析和生成 CSV 数据进行细粒度控制。但是,对于许多需求,更简单的方法就足够了。
本节总结了 CSV 中允许您在不显式创建 CSV 对象的情况下进行解析和生成的单例方法。有关详细信息,请单击链接。
简单解析¶ ↑
解析方法通常返回以下两种方法之一
-
字符串数组的数组
-
外部数组是整个“表格”。
-
每个内部数组都是一行。
-
每个字符串都是一个字段。
-
-
CSV::Table
对象。有关详细信息,请参阅带有标题的 CSV。
解析字符串¶ ↑
要解析的输入可以是字符串
string = "foo,0\nbar,1\nbaz,2\n"
方法 CSV.parse
返回整个 CSV 数据
CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
方法 CSV.parse_line
仅返回第一行
CSV.parse_line(string) # => ["foo", "0"]
CSV 使用实例方法 String#parse_csv 扩展类 String,该方法也仅返回第一行
string.parse_csv # => ["foo", "0"]
通过文件路径进行解析¶ ↑
要解析的输入可以位于文件中
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
方法 CSV.read
返回整个 CSV 数据
CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
方法 CSV.foreach
进行迭代,将每一行传递给给定的块
CSV.foreach(path) do |row| p row end
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
方法 CSV.table
将整个 CSV 数据作为 CSV::Table
对象返回
CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:3>
从打开的 IO 流中进行解析¶ ↑
要解析的输入可以位于打开的 IO 流中
方法 CSV.read
返回整个 CSV 数据
File.open(path) do |file| CSV.read(file) end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
方法 CSV.parse
也是如此
File.open(path) do |file| CSV.parse(file) end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
方法 CSV.parse_line
仅返回第一行
File.open(path) do |file| CSV.parse_line(file) end # => ["foo", "0"]
方法 CSV.foreach
进行迭代,将每一行传递给给定的块
File.open(path) do |file| CSV.foreach(file) do |row| p row end end
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
方法 CSV.table
将整个 CSV 数据作为 CSV::Table
对象返回
File.open(path) do |file| CSV.table(file) end # => #<CSV::Table mode:col_or_row row_count:3>
简单生成¶ ↑
方法 CSV.generate
返回一个字符串;此示例使用 CSV#<<
方法来追加要生成的那些行
output_string = CSV.generate do |csv| csv << ['foo', 0] csv << ['bar', 1] csv << ['baz', 2] end output_string # => "foo,0\nbar,1\nbaz,2\n"
方法 CSV.generate_line
返回一个字符串,其中包含由数组构造的单行
CSV.generate_line(['foo', '0']) # => "foo,0\n"
CSV 使用实例方法 Array#to_csv
扩展了类 Array,该方法将数组转换为字符串
['foo', '0'].to_csv # => "foo,0\n"
“筛选”CSV¶ ↑
方法 CSV.filter
为 CSV 数据提供 Unix 风格的筛选器。处理输入数据以形成输出数据
in_string = "foo,0\nbar,1\nbaz,2\n" out_string = '' CSV.filter(in_string, out_string) do |row| row[0] = row[0].upcase row[1] *= 4 end out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
CSV 对象¶ ↑
有三种方法可以创建 CSV 对象
-
方法
CSV.new
返回一个新的 CSV 对象。 -
方法
CSV.instance
返回一个新的或缓存的 CSV 对象。 -
方法 CSV() 也返回一个新的或缓存的 CSV 对象。
实例方法¶ ↑
CSV 有三组实例方法
-
它自己内部定义的实例方法。
-
模块
Enumerable
包含的方法。 -
委托给类
IO
的方法。请参见下文。
委托方法¶ ↑
为了方便起见,CSV
对象将委托给类 IO
中的许多方法。(一些方法在 CSV 中有包装“保护代码”。)您可以调用
-
IO#string
-
IO#truncate
选项¶ ↑
选项的默认值为
DEFAULT_OPTIONS = { # For both parsing and generating. col_sep: ",", row_sep: :auto, quote_char: '"', # For parsing. field_size_limit: nil, converters: nil, unconverted_fields: nil, headers: false, return_headers: false, header_converters: nil, skip_blanks: false, skip_lines: nil, liberal_parsing: false, nil_value: nil, empty_value: "", strip: false, # For generating. write_headers: nil, quote_empty: true, force_quotes: false, write_converters: nil, write_nil_value: nil, write_empty_value: "", }
解析选项¶ ↑
下面详细描述了解析选项,包括
-
row_sep
:指定行分隔符;用于分隔行。 -
col_sep
:指定列分隔符;用于分隔字段。 -
quote_char
:指定引用字符;用于引用字段。 -
field_size_limit
:指定允许的最大字段大小 + 1。自 3.2.3 起已弃用。请改用max_field_size
。 -
max_field_size
:指定允许的最大字段大小。 -
converters
:指定要使用的字段转换器。 -
unconverted_fields
:指定是否可以使用未转换的字段。 -
headers
:指定数据是否包含标题,或指定标题本身。 -
return_headers
:指定是否返回标题。 -
header_converters
:指定要使用的标题转换器。 -
skip_blanks
:指定是否忽略空白行。 -
skip_lines
:指定如何识别注释行。 -
strip
:指定是否从字段中删除前导和尾随空白。这必须与col_sep
兼容;如果不兼容,则会引发ArgumentError
异常。 -
liberal_parsing
:指定 CSV 是否应尝试解析不兼容的数据。 -
nil_value
:指定要替换每个空(无文本)字段的对象。 -
empty_value
:指定要替换每个空字段的对象。
选项 row_sep
¶ ↑
指定行分隔符(字符串或符号 :auto
(见下文))用于解析和生成。
默认值
CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto
当 row_sep
为字符串时,该字符串将成为行分隔符。String
在使用前将被转码为数据的 Encoding
。
使用 "\n"
row_sep = "\n" str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 |
(管道)
row_sep = '|' str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0|bar,1|baz,2|" ary = CSV.parse(str, row_sep: row_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 --
(两个连字符)
row_sep = '--' str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0--bar,1--baz,2--" ary = CSV.parse(str, row_sep: row_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 ''
(空字符串)
row_sep = '' str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0bar,1baz,2" ary = CSV.parse(str, row_sep: row_sep) ary # => [["foo", "0bar", "1baz", "2"]]
当 row_sep
是符号 :auto
(默认值)时,生成会使用 "\n"
作为行分隔符
str = CSV.generate do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n"
另一方面,解析会调用行分隔符的自动发现。
自动发现会提前读取数据,寻找下一个 \r\n
、\n
或 \r
序列。即使该序列出现在带引号的字段中,也会选择该序列,假设您会在那里有相同的行尾。
示例
str = CSV.generate do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
如果以下任一条件为真,则使用默认的 $INPUT_RECORD_SEPARATOR
($/
)
-
未找到任何这些序列。
-
Data
是ARGF
、STDIN
、STDOUT
或STDERR
。 -
流仅可用于输出。
显然,发现需要一点时间。如果速度很重要,请手动 Set
。另请注意,如果要使用此功能,应在 Windows 上以二进制模式打开 IO
对象,因为行尾转换可能会导致将文档位置重置为读取前的位置时出现问题。
选项 col_sep
¶ ↑
指定用于解析和生成的字符串字段分隔符。该字符串在使用前将被转码为数据的编码。
默认值
CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma)
使用默认值(逗号)
str = CSV.generate do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 :
(冒号)
col_sep = ':' str = CSV.generate(col_sep: col_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo:0\nbar:1\nbaz:2\n" ary = CSV.parse(str, col_sep: col_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 ::
(两个冒号)
col_sep = '::' str = CSV.generate(col_sep: col_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo::0\nbar::1\nbaz::2\n" ary = CSV.parse(str, col_sep: col_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 ''
(空字符串)
col_sep = '' str = CSV.generate(col_sep: col_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo0\nbar1\nbaz2\n"
使用空字符串解析时引发异常
col_sep = '' # Raises ArgumentError (:col_sep must be 1 or more characters: "") CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep)
选项 quote_char
¶ ↑
指定用于在解析和生成中引用字段的字符(长度为 1 的字符串)。此 String
在使用前将被转码为数据的编码。
默认值
CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)
这对于错误地使用 '
(单引号)而不是正确的 "
(双引号)引用字段的应用程序很有用。
使用默认值(双引号)
str = CSV.generate do |csv| csv << ['foo', 0] csv << ["'bar'", 1] csv << ['"baz"', 2] end str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]
使用 '
(单引号)
quote_char = "'" str = CSV.generate(quote_char: quote_char) do |csv| csv << ['foo', 0] csv << ["'bar'", 1] csv << ['"baz"', 2] end str # => "foo,0\n'''bar''',1\n\"baz\",2\n" ary = CSV.parse(str, quote_char: quote_char) ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]
如果字符串长度大于 1,则引发异常
# Raises ArgumentError (:quote_char has to be nil or a single character String) CSV.new('', quote_char: 'xx')
如果值不是字符串,则引发异常
# Raises ArgumentError (:quote_char has to be nil or a single character String) CSV.new('', quote_char: :foo)
选项 field_size_limit
¶ ↑
指定整数字段大小限制。
默认值
CSV::DEFAULT_OPTIONS.fetch(:field_size_limit) # => nil
这是 CSV
将提前读取以查找字段的结束引号的最大大小。(实际上,它读取到超出此大小的第一个行尾。)如果在限制内找不到引号,CSV
将引发 MalformedCSVError
,假设数据有缺陷。您可以使用此限制来防止对解析器的有效 DoS 攻击。但是,此限制可能会导致合法的解析失败;因此,默认值为 nil
(无限制)。
本节中的示例
str = <<~EOT "a","b" " 2345 ","" EOT str # => "\"a\",\"b\"\n\"\n2345\n\",\"\"\n"
使用默认的nil
ary = CSV.parse(str) ary # => [["a", "b"], ["\n2345\n", ""]]
使用50
field_size_limit = 50 ary = CSV.parse(str, field_size_limit: field_size_limit) ary # => [["a", "b"], ["\n2345\n", ""]]
如果字段太长,则引发异常
big_str = "123456789\n" * 1024 # Raises CSV::MalformedCSVError (Field size exceeded in line 1.) CSV.parse('valid,fields,"' + big_str + '"', field_size_limit: 2048)
选项converters
¶ ↑
指定用于解析字段的转换器。请参见字段转换器
默认值
CSV::DEFAULT_OPTIONS.fetch(:converters) # => nil
该值可以是字段转换器名称(请参见存储的转换器)
str = '1,2,3' # Without a converter array = CSV.parse_line(str) array # => ["1", "2", "3"] # With built-in converter :integer array = CSV.parse_line(str, converters: :integer) array # => [1, 2, 3]
该值可以是转换器列表(请参见转换器列表)
str = '1,3.14159' # Without converters array = CSV.parse_line(str) array # => ["1", "3.14159"] # With built-in converters array = CSV.parse_line(str, converters: [:integer, :float]) array # => [1, 3.14159]
该值可以是 Proc 自定义转换器:(请参见自定义字段转换器)
str = ' foo , bar , baz ' # Without a converter array = CSV.parse_line(str) array # => [" foo ", " bar ", " baz "] # With a custom converter array = CSV.parse_line(str, converters: proc {|field| field.strip }) array # => ["foo", "bar", "baz"]
另请参见自定义字段转换器
如果转换器不是转换器名称或 Proc,则引发异常
str = 'foo,0' # Raises NoMethodError (undefined method `arity' for nil:NilClass) CSV.parse(str, converters: :foo)
选项unconverted_fields
¶ ↑
指定确定是否可用未转换字段值的布尔值。
默认值
CSV::DEFAULT_OPTIONS.fetch(:unconverted_fields) # => nil
未转换的字段值是源数据中发现的值,在通过选项converters
执行任何转换之前。
当选项unconverted_fields
为true
时,每个返回的行(Array 或 CSV::Row)都有一个添加的方法unconverted_fields
,它返回未转换的字段值
str = <<-EOT foo,0 bar,1 baz,2 EOT # Without unconverted_fields csv = CSV.parse(str, converters: :integer) csv # => [["foo", 0], ["bar", 1], ["baz", 2]] csv.first.respond_to?(:unconverted_fields) # => false # With unconverted_fields csv = CSV.parse(str, converters: :integer, unconverted_fields: true) csv # => [["foo", 0], ["bar", 1], ["baz", 2]] csv.first.respond_to?(:unconverted_fields) # => true csv.first.unconverted_fields # => ["foo", "0"]
选项headers
¶ ↑
指定要用于定义列标题的布尔值、符号、数组或字符串。
默认值
CSV::DEFAULT_OPTIONS.fetch(:headers) # => false
没有headers
str = <<-EOT Name,Count foo,0 bar,1 bax,2 EOT csv = CSV.new(str) csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\""> csv.headers # => nil csv.shift # => ["Name", "Count"]
如果设置为true
或符号:first_row
,则第一行数据将被视为一行标题
str = <<-EOT Name,Count foo,0 bar,1 bax,2 EOT csv = CSV.new(str, headers: true) csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:2 col_sep:"," row_sep:"\n" quote_char:"\"" headers:["Name", "Count"]> csv.headers # => ["Name", "Count"] csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
如果设置为数组,则数组元素将被视为标题
str = <<-EOT foo,0 bar,1 bax,2 EOT csv = CSV.new(str, headers: ['Name', 'Count']) csv csv.headers # => ["Name", "Count"] csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
如果设置为字符串str
,则使用当前options
调用方法CSV::parse_line(str, options)
,并将返回的数组视为标题
str = <<-EOT foo,0 bar,1 bax,2 EOT csv = CSV.new(str, headers: 'Name,Count') csv csv.headers # => ["Name", "Count"] csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
选项return_headers
¶ ↑
指定确定方法shift
返回还是忽略标题行的布尔值。
默认值
CSV::DEFAULT_OPTIONS.fetch(:return_headers) # => false
示例
str = <<-EOT Name,Count foo,0 bar,1 bax,2 EOT # Without return_headers first row is str. csv = CSV.new(str, headers: true) csv.shift # => #<CSV::Row "Name":"foo" "Count":"0"> # With return_headers first row is headers. csv = CSV.new(str, headers: true, return_headers: true) csv.shift # => #<CSV::Row "Name":"Name" "Count":"Count">
选项header_converters
¶ ↑
指定用于解析标题的转换器。请参见标题转换器
默认值
CSV::DEFAULT_OPTIONS.fetch(:header_converters) # => nil
与选项converters的功能相同,但
-
转换器仅适用于标题行。
-
内置标题转换器为
:downcase
和:symbol
。
本部分假设已执行以下操作
str = <<-EOT Name,Value foo,0 bar,1 baz,2 EOT # With no header converter table = CSV.parse(str, headers: true) table.headers # => ["Name", "Value"]
该值可能是一个标题转换器名称(请参阅存储的转换器)
table = CSV.parse(str, headers: true, header_converters: :downcase) table.headers # => ["name", "value"]
该值可以是转换器列表(请参见转换器列表)
header_converters = [:downcase, :symbol] table = CSV.parse(str, headers: true, header_converters: header_converters) table.headers # => [:name, :value]
该值可能是一个 Proc 自定义转换器(请参阅自定义标题转换器)
upcase_converter = proc {|field| field.upcase } table = CSV.parse(str, headers: true, header_converters: upcase_converter) table.headers # => ["NAME", "VALUE"]
另请参阅自定义标题转换器
选项 skip_blanks
¶ ↑
指定一个布尔值,用于确定是否忽略输入中的空行;包含列分隔符的行不被视为空白。
默认值
CSV::DEFAULT_OPTIONS.fetch(:skip_blanks) # => false
另请参阅选项skiplines。
有关本部分中的示例
str = <<-EOT foo,0 bar,1 baz,2 , EOT
使用默认值 false
ary = CSV.parse(str) ary # => [["foo", "0"], [], ["bar", "1"], ["baz", "2"], [], [nil, nil]]
使用 true
ary = CSV.parse(str, skip_blanks: true) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]
使用真值
ary = CSV.parse(str, skip_blanks: :foo) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]
选项 skip_lines
¶ ↑
指定一个对象,用于识别输入中要忽略的注释行
-
如果为正则表达式,则忽略与之匹配的行。
-
如果为字符串,则将其转换为正则表达式,忽略与之匹配的行。
-
如果为
nil
,则不将任何行视为注释。
默认值
CSV::DEFAULT_OPTIONS.fetch(:skip_lines) # => nil
有关本部分中的示例
str = <<-EOT # Comment foo,0 bar,1 baz,2 # Another comment EOT str # => "# Comment\nfoo,0\nbar,1\nbaz,2\n# Another comment\n"
使用默认值 nil
ary = CSV.parse(str) ary # => [["# Comment"], ["foo", "0"], ["bar", "1"], ["baz", "2"], ["# Another comment"]]
使用正则表达式
ary = CSV.parse(str, skip_lines: /^#/) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用字符串
ary = CSV.parse(str, skip_lines: '#') ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
如果给定的对象不是正则表达式、字符串或 nil
,则引发异常
# Raises ArgumentError (:skip_lines has to respond to #match: 0) CSV.parse(str, skip_lines: 0)
选项 strip
¶ ↑
指定布尔值,用于确定是否从每个输入字段中删除空白。
默认值
CSV::DEFAULT_OPTIONS.fetch(:strip) # => false
默认值为 false
ary = CSV.parse_line(' a , b ') ary # => [" a ", " b "]
值为 true
ary = CSV.parse_line(' a , b ', strip: true) ary # => ["a", "b"]
选项 liberal_parsing
¶ ↑
指定布尔值或哈希值,用于确定CSV
是否尝试解析不符合 RFC 4180 的输入,例如未加引号的字段中的双引号。
默认值
CSV::DEFAULT_OPTIONS.fetch(:liberal_parsing) # => false
对于接下来的两个示例
str = 'is,this "three, or four",fields'
不使用 liberal_parsing
# Raises CSV::MalformedCSVError (Illegal quoting in str 1.) CSV.parse_line(str)
使用 liberal_parsing
ary = CSV.parse_line(str, liberal_parsing: true) ary # => ["is", "this \"three", " or four\"", "fields"]
使用 backslash_quote
子选项解析使用反斜杠转义双引号字符的值。这会导致解析器将 \"
视为 ""
。
对于接下来的两个示例
str = 'Show,"Harry \"Handcuff\" Houdini, the one and only","Tampa Theater"'
使用 liberal_parsing
,但不使用 backslash_quote
子选项
# Incorrect interpretation of backslash; incorrectly interprets the quoted comma as a field separator. ary = CSV.parse_line(str, liberal_parsing: true) ary # => ["Show", "\"Harry \\\"Handcuff\\\" Houdini", " the one and only\"", "Tampa Theater"] puts ary[1] # => "Harry \"Handcuff\" Houdini
使用 liberal_parsing
及其 backslash_quote
子选项
ary = CSV.parse_line(str, liberal_parsing: { backslash_quote: true }) ary # => ["Show", "Harry \"Handcuff\" Houdini, the one and only", "Tampa Theater"] puts ary[1] # => Harry "Handcuff" Houdini, the one and only
选项 nil_value
¶ ↑
指定要替换每个空(无文本)字段的对象。
默认值
CSV::DEFAULT_OPTIONS.fetch(:nil_value) # => nil
使用默认值 nil
CSV.parse_line('a,,b,,c') # => ["a", nil, "b", nil, "c"]
使用不同的对象
CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"]
选项 empty_value
¶ ↑
指定要替换每个包含空字符串的字段的对象。
默认值
CSV::DEFAULT_OPTIONS.fetch(:empty_value) # => "" (empty string)
使用默认值 ""
CSV.parse_line('a,"",b,"",c') # => ["a", "", "b", "", "c"]
使用不同的对象
CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"]
生成选项¶ ↑
下面详细描述的生成选项包括
-
row_sep
:指定行分隔符;用于分隔行。 -
col_sep
:指定列分隔符;用于分隔字段。 -
quote_char
:指定引用字符;用于引用字段。 -
write_headers
: 指定是否要写入标题。 -
force_quotes
: 指定是否要引用每个输出字段。 -
quote_empty
: 指定是否要引用每个空输出字段。 -
write_converters
: 指定要在写入时使用的字段转换器。 -
write_nil_value
: 指定要替换每个nil
值字段的对象。 -
write_empty_value
: 指定要替换每个空字段的对象。
选项 row_sep
¶ ↑
指定行分隔符(字符串或符号 :auto
(见下文))用于解析和生成。
默认值
CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto
当 row_sep
为字符串时,该字符串将成为行分隔符。String
在使用前将被转码为数据的 Encoding
。
使用 "\n"
row_sep = "\n" str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 |
(管道)
row_sep = '|' str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0|bar,1|baz,2|" ary = CSV.parse(str, row_sep: row_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 --
(两个连字符)
row_sep = '--' str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0--bar,1--baz,2--" ary = CSV.parse(str, row_sep: row_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 ''
(空字符串)
row_sep = '' str = CSV.generate(row_sep: row_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0bar,1baz,2" ary = CSV.parse(str, row_sep: row_sep) ary # => [["foo", "0bar", "1baz", "2"]]
当 row_sep
是符号 :auto
(默认值)时,生成会使用 "\n"
作为行分隔符
str = CSV.generate do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n"
另一方面,解析会调用行分隔符的自动发现。
自动发现会提前读取数据,寻找下一个 \r\n
、\n
或 \r
序列。即使该序列出现在带引号的字段中,也会选择该序列,假设您会在那里有相同的行尾。
示例
str = CSV.generate do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
如果以下任一条件为真,则使用默认的 $INPUT_RECORD_SEPARATOR
($/
)
-
未找到任何这些序列。
-
Data
是ARGF
、STDIN
、STDOUT
或STDERR
。 -
流仅可用于输出。
显然,发现需要一点时间。如果速度很重要,请手动 Set
。另请注意,如果要使用此功能,应在 Windows 上以二进制模式打开 IO
对象,因为行尾转换可能会导致将文档位置重置为读取前的位置时出现问题。
选项 col_sep
¶ ↑
指定用于解析和生成的字符串字段分隔符。该字符串在使用前将被转码为数据的编码。
默认值
CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma)
使用默认值(逗号)
str = CSV.generate do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 :
(冒号)
col_sep = ':' str = CSV.generate(col_sep: col_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo:0\nbar:1\nbaz:2\n" ary = CSV.parse(str, col_sep: col_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 ::
(两个冒号)
col_sep = '::' str = CSV.generate(col_sep: col_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo::0\nbar::1\nbaz::2\n" ary = CSV.parse(str, col_sep: col_sep) ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
使用 ''
(空字符串)
col_sep = '' str = CSV.generate(col_sep: col_sep) do |csv| csv << [:foo, 0] csv << [:bar, 1] csv << [:baz, 2] end str # => "foo0\nbar1\nbaz2\n"
使用空字符串解析时引发异常
col_sep = '' # Raises ArgumentError (:col_sep must be 1 or more characters: "") CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep)
选项 quote_char
¶ ↑
指定用于在解析和生成中引用字段的字符(长度为 1 的字符串)。此 String
在使用前将被转码为数据的编码。
默认值
CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)
这对于错误地使用 '
(单引号)而不是正确的 "
(双引号)引用字段的应用程序很有用。
使用默认值(双引号)
str = CSV.generate do |csv| csv << ['foo', 0] csv << ["'bar'", 1] csv << ['"baz"', 2] end str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n" ary = CSV.parse(str) ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]
使用 '
(单引号)
quote_char = "'" str = CSV.generate(quote_char: quote_char) do |csv| csv << ['foo', 0] csv << ["'bar'", 1] csv << ['"baz"', 2] end str # => "foo,0\n'''bar''',1\n\"baz\",2\n" ary = CSV.parse(str, quote_char: quote_char) ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]
如果字符串长度大于 1,则引发异常
# Raises ArgumentError (:quote_char has to be nil or a single character String) CSV.new('', quote_char: 'xx')
如果值不是字符串,则引发异常
# Raises ArgumentError (:quote_char has to be nil or a single character String) CSV.new('', quote_char: :foo)
选项 write_headers
¶ ↑
指定确定输出中是否包含标题行的布尔值;如果没有标题,则忽略。
默认值
CSV::DEFAULT_OPTIONS.fetch(:write_headers) # => nil
不使用 write_headers
file_path = 't.csv' CSV.open(file_path,'w', :headers => ['Name','Value'] ) do |csv| csv << ['foo', '0'] end CSV.open(file_path) do |csv| csv.shift end # => ["foo", "0"]
使用 write_headers
“
CSV.open(file_path,'w', :write_headers => true, :headers => ['Name','Value'] ) do |csv| csv << ['foo', '0'] end CSV.open(file_path) do |csv| csv.shift end # => ["Name", "Value"]
选项 force_quotes
¶ ↑
指定确定是否对每个输出字段使用双引号的布尔值。
默认值
CSV::DEFAULT_OPTIONS.fetch(:force_quotes) # => false
有关本部分中的示例
ary = ['foo', 0, nil]
使用默认值 false
str = CSV.generate_line(ary) str # => "foo,0,\n"
使用 true
str = CSV.generate_line(ary, force_quotes: true) str # => "\"foo\",\"0\",\"\"\n"
选项 quote_empty
¶ ↑
指定确定是否对空值使用双引号的布尔值。
默认值
CSV::DEFAULT_OPTIONS.fetch(:quote_empty) # => true
使用默认值 true
CSV.generate_line(['"', ""]) # => "\"\"\"\",\"\"\n"
使用 false
CSV.generate_line(['"', ""], quote_empty: false) # => "\"\"\"\",\n"
选项 write_converters
¶ ↑
指定在生成字段时要使用的转换器。请参阅 写入转换器
默认值
CSV::DEFAULT_OPTIONS.fetch(:write_converters) # => nil
不使用写入转换器
str = CSV.generate_line(["\na\n", "\tb\t", " c "]) str # => "\"\na\n\",\tb\t, c \n"
使用写入转换器
strip_converter = proc {|field| field.strip } str = CSV.generate_line(["\na\n", "\tb\t", " c "], write_converters: strip_converter) str # => "a,b,c\n"
使用两个写入转换器(按顺序调用)
upcase_converter = proc {|field| field.upcase } downcase_converter = proc {|field| field.downcase } write_converters = [upcase_converter, downcase_converter] str = CSV.generate_line(['a', 'b', 'c'], write_converters: write_converters) str # => "a,b,c\n"
另请参阅 写入转换器
选项 write_nil_value
¶ ↑
指定要替换每个 nil
值字段的对象。
默认值
CSV::DEFAULT_OPTIONS.fetch(:write_nil_value) # => nil
不使用该选项
str = CSV.generate_line(['a', nil, 'c', nil]) str # => "a,,c,\n"
使用该选项
str = CSV.generate_line(['a', nil, 'c', nil], write_nil_value: "x") str # => "a,x,c,x\n"
选项 write_empty_value
¶ ↑
指定要替换每个包含空字符串的字段的对象。
默认值
CSV::DEFAULT_OPTIONS.fetch(:write_empty_value) # => ""
不使用该选项
str = CSV.generate_line(['a', '', 'c', '']) str # => "a,\"\",c,\"\"\n"
使用该选项
str = CSV.generate_line(['a', '', 'c', ''], write_empty_value: "x") str # => "a,x,c,x\n"
带标题的 CSV¶ ↑
CSV
允许指定 CSV
文件的列名,无论它们是在数据中还是单独提供的。如果指定了标题,则读取方法将返回 CSV::Table
的实例,该实例由 CSV::Row
组成。
# Headers are part of data data = CSV.parse(<<~ROWS, headers: true) Name,Department,Salary Bob,Engineering,1000 Jane,Sales,2000 John,Management,5000 ROWS data.class #=> CSV::Table data.first #=> #<CSV::Row "Name":"Bob" "Department":"Engineering" "Salary":"1000"> data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"} # Headers provided by developer data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary]) data.first #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">
转换器¶ ↑
默认情况下,CSV 解析的每个值(字段或标题)都形成一个字符串。你可以使用字段转换器或标题转换器来拦截和修改解析的值
同样,默认情况下,在生成期间要写入的每个值都将“按原样”写入。你可以使用写入转换器在写入之前修改值。
-
参见 写入转换器。
指定转换器¶ ↑
您可以在各种 CSV 方法的 options
参数中指定用于解析或生成的转换器
-
选项
converters
用于转换已解析的字段值。 -
选项
header_converters
用于转换已解析的头字段值。 -
选项
write_converters
用于转换要写入(生成)的值。
有三种形式可用于指定转换器
-
转换器 proc:用于转换的可执行代码。
-
转换器名称:存储的转换器的名称。
-
转换器列表:转换器 proc、转换器名称和转换器列表的数组。
转换器 Proc¶ ↑
此转换器 proc strip_converter
接受一个值 field
并返回 field.strip
strip_converter = proc {|field| field.strip }
在此对 CSV.parse
的调用中,关键字参数 converters: string_converter
指定
-
Proc
string_converter
将针对每个已解析字段调用。 -
转换器的返回值将替换
field
值。
示例
string = " foo , 0 \n bar , 1 \n baz , 2 \n" array = CSV.parse(string, converters: strip_converter) array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
转换器 proc 可以接收第二个参数 field_info
,其中包含有关该字段的详细信息。此修改后的 strip_converter
显示其参数
strip_converter = proc do |field, field_info| p [field, field_info] field.strip end string = " foo , 0 \n bar , 1 \n baz , 2 \n" array = CSV.parse(string, converters: strip_converter) array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
输出
[" foo ", #<struct CSV::FieldInfo index=0, line=1, header=nil>] [" 0 ", #<struct CSV::FieldInfo index=1, line=1, header=nil>] [" bar ", #<struct CSV::FieldInfo index=0, line=2, header=nil>] [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>] [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>] [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
每个 CSV::FieldInfo
对象显示
-
基于 0 的字段索引。
-
基于 1 的行索引。
-
字段头字段(如果存在)。
存储的转换器¶ ↑
可以为转换器指定名称并将其存储在解析方法可以按名称找到它的结构中。
字段转换器的存储结构是哈希 CSV::Converters
。它具有多个内置转换器 proc
-
:integer
:将每个嵌入字符串的整数转换为真正的整数。 -
:float
:将每个嵌入字符串的浮点数转换为真正的浮点数。 -
:date
:将每个嵌入字符串的日期转换为真正的日期。 -
:date_time
:将每个嵌入字符串的日期时间转换为真正的日期时间
. 此示例创建一个转换器 proc,然后将其存储
strip_converter = proc {|field| field.strip } CSV::Converters[:strip] = strip_converter
然后,解析方法调用可以通过其名称 :strip
引用转换器
string = " foo , 0 \n bar , 1 \n baz , 2 \n" array = CSV.parse(string, converters: :strip) array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
头转换器的存储结构是哈希 CSV::HeaderConverters
,其工作方式相同。它还内置了转换器过程
-
:downcase
:将每个头转换为小写。 -
:symbol
:将每个头转换为符号。
对于写入头,没有这样的存储结构。
为了让解析方法能够访问非主 Ractor 中的存储转换器,必须首先使存储结构可共享。因此,必须在使用存储在这些结构中的转换器的 Ractor 创建之前调用 Ractor.make_shareable(CSV::Converters)
和 Ractor.make_shareable(CSV::HeaderConverters)
。(由于使存储结构可共享涉及冻结它们,因此必须首先添加要使用的任何自定义转换器。)
转换器列表¶ ↑
转换器列表是一个数组,其中可能包含任何组合的
-
转换器过程。
-
存储转换器的名称。
-
嵌套转换器列表。
示例
numeric_converters = [:integer, :float] date_converters = [:date, :date_time] [numeric_converters, strip_converter] [strip_converter, date_converters, :float]
与转换器过程类似,转换器列表可以命名并存储在 CSV::Converters 或 CSV::HeaderConverters
中
CSV::Converters[:custom] = [strip_converter, date_converters, :float] CSV::HeaderConverters[:custom] = [:downcase, :symbol]
有两个内置转换器列表
CSV::Converters[:numeric] # => [:integer, :float] CSV::Converters[:all] # => [:date_time, :numeric]
字段转换器¶ ↑
如果不进行转换,所有行中的所有已解析字段都将变为字符串
string = "foo,0\nbar,1\nbaz,2\n" ary = CSV.parse(string) ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
指定字段转换器时,每个已解析字段都会传递给转换器;其返回值将成为字段的存储值。例如,转换器可能将嵌入在字符串中的整数转换为真正的整数。(事实上,内置字段转换器 :integer
就是这么做的。)
有三种方法可以使用字段转换器。
-
使用解析方法的选项 converters
ary = CSV.parse(string, converters: :integer) ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]]
-
使用新 CSV 实例的选项 converters
csv = CSV.new(string, converters: :integer) # Field converters in effect: csv.converters # => [:integer] csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
-
使用方法
convert
向 CSV 实例添加字段转换器csv = CSV.new(string) # Add a converter. csv.convert(:integer) csv.converters # => [:integer] csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
安装字段转换器不会影响已读取的行
csv = CSV.new(string) csv.shift # => ["foo", "0"] # Add a converter. csv.convert(:integer) csv.converters # => [:integer] csv.read # => [["bar", 1], ["baz", 2]]
还有其他内置转换器,并且还支持自定义转换器。
内置字段转换器¶ ↑
内置字段转换器位于哈希 CSV::Converters
中
-
每个键都是字段转换器名称。
-
每个值都是以下之一
-
过程字段转换器。
-
字段转换器名称的数组。
-
显示
CSV::Converters.each_pair do |name, value| if value.kind_of?(Proc) p [name, value.class] else p [name, value] end end
输出
[:integer, Proc] [:float, Proc] [:numeric, [:integer, :float]] [:date, Proc] [:date_time, Proc] [:all, [:date_time, :numeric]]
在尝试转换之前,所有这些转换器都会将值转码为 UTF-8。如果无法将值转码为 UTF-8,则转换将失败,并且值将保持未转换状态。
转换器 :integer
转换 Integer() 接受的每个字段
data = '0,1,2,x' # Without the converter csv = CSV.parse_line(data) csv # => ["0", "1", "2", "x"] # With the converter csv = CSV.parse_line(data, converters: :integer) csv # => [0, 1, 2, "x"]
转换器 :float
转换 Float() 接受的每个字段
data = '1.0,3.14159,x' # Without the converter csv = CSV.parse_line(data) csv # => ["1.0", "3.14159", "x"] # With the converter csv = CSV.parse_line(data, converters: :float) csv # => [1.0, 3.14159, "x"]
转换器 :numeric
使用 :integer
和 :float
进行转换。
转换器 :date
转换 Date::parse
接受的每个字段
data = '2001-02-03,x' # Without the converter csv = CSV.parse_line(data) csv # => ["2001-02-03", "x"] # With the converter csv = CSV.parse_line(data, converters: :date) csv # => [#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>, "x"]
转换器 :date_time
转换 DateTime::parse
接受的每个字段
data = '2020-05-07T14:59:00-05:00,x' # Without the converter csv = CSV.parse_line(data) csv # => ["2020-05-07T14:59:00-05:00", "x"] # With the converter csv = CSV.parse_line(data, converters: :date_time) csv # => [#<DateTime: 2020-05-07T14:59:00-05:00 ((2458977j,71940s,0n),-18000s,2299161j)>, "x"]
转换器 :numeric
同时使用 :date_time
和 :numeric
进行转换。
如上所示,方法 convert
向 CSV 实例添加转换器,方法 converters
返回一个有效的转换器数组
csv = CSV.new('0,1,2') csv.converters # => [] csv.convert(:integer) csv.converters # => [:integer] csv.convert(:date) csv.converters # => [:integer, :date]
自定义字段转换器¶ ↑
你可以定义一个自定义字段转换器
strip_converter = proc {|field| field.strip } string = " foo , 0 \n bar , 1 \n baz , 2 \n" array = CSV.parse(string, converters: strip_converter) array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
你可以在转换器 Hash 中注册转换器,这样你就可以通过名称引用它
CSV::Converters[:strip] = strip_converter string = " foo , 0 \n bar , 1 \n baz , 2 \n" array = CSV.parse(string, converters: :strip) array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
标题转换器¶ ↑
标题转换器仅对标题(而不是其他行)进行操作。
有三种方法可以使用标题转换器;这些示例使用内置标题转换器 :downcase
,它将每个解析的标题变为小写。
-
使用单例解析方法的选项
header_converters
string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2" tbl = CSV.parse(string, headers: true, header_converters: :downcase) tbl.class # => CSV::Table tbl.headers # => ["name", "count"]
-
使用新 CSV 实例的选项
header_converters
csv = CSV.new(string, header_converters: :downcase) # Header converters in effect: csv.header_converters # => [:downcase] tbl = CSV.parse(string, headers: true) tbl.headers # => ["Name", "Count"]
-
方法
header_convert
向 CSV 实例添加一个标题转换器csv = CSV.new(string) # Add a header converter. csv.header_convert(:downcase) csv.header_converters # => [:downcase] tbl = CSV.parse(string, headers: true) tbl.headers # => ["Name", "Count"]
内置标题转换器¶ ↑
内置标题转换器在 Hash CSV::HeaderConverters
中。那里的键是转换器的名称
CSV::HeaderConverters.keys # => [:downcase, :symbol]
转换器 :downcase
通过将每个标题变为小写来转换
string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2" tbl = CSV.parse(string, headers: true, header_converters: :downcase) tbl.class # => CSV::Table tbl.headers # => ["name", "count"]
转换器 :symbol
通过将每个标题转换为符号来转换
string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2" tbl = CSV.parse(string, headers: true, header_converters: :symbol) tbl.headers # => [:name, :count]
详细信息
-
去除前导和尾随空格。
-
将标题变为小写。
-
用下划线替换嵌入的空格。
-
移除非单词字符。
-
将字符串转换为符号。
自定义标题转换器¶ ↑
你可以定义一个自定义标题转换器
upcase_converter = proc {|header| header.upcase } string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" table = CSV.parse(string, headers: true, header_converters: upcase_converter) table # => #<CSV::Table mode:col_or_row row_count:4> table.headers # => ["NAME", "VALUE"]
你可以在 HeaderConverters Hash 中注册转换器,这样你就可以通过名称引用它
CSV::HeaderConverters[:upcase] = upcase_converter table = CSV.parse(string, headers: true, header_converters: :upcase) table # => #<CSV::Table mode:col_or_row row_count:4> table.headers # => ["NAME", "VALUE"]
写入转换器¶ ↑
当您为生成 CSV 指定一个写入转换器时,每个要写入的字段都会传递给转换器;它的返回值成为字段的新值。例如,转换器可能会从字段中去除空格。
不使用写入转换器(所有字段未修改)
output_string = CSV.generate do |csv| csv << [' foo ', 0] csv << [' bar ', 1] csv << [' baz ', 2] end output_string # => " foo ,0\n bar ,1\n baz ,2\n"
使用选项 write_converters
和两个自定义写入转换器
strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field } upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field } write_converters = [strip_converter, upcase_converter] output_string = CSV.generate(write_converters: write_converters) do |csv| csv << [' foo ', 0] csv << [' bar ', 1] csv << [' baz ', 2] end output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
字符编码(M17n 或多语言化)¶ ↑
这个新的 CSV
解析器精通 m17n。解析器在正在读写 IO
或 String
对象的 Encoding
中工作。您的数据永远不会被转码(除非您要求 Ruby 为您转码),并且将以其所在的 Encoding
进行解析。因此,CSV
将返回 Encoding
中的字符串数组或行。这是通过将解析器本身转码到您的 Encoding
中来实现的。
当然,为了实现这种多编码支持,必须进行一些转码。例如,:col_sep
、:row_sep
和 :quote_char
必须转码以匹配您的数据。希望这会让整个过程感觉透明,因为 CSV 的默认值应该只对您的数据起作用。但是,您可以在目标 Encoding
中手动设置这些值以避免翻译。
同样重要的是要注意,虽然 CSV 的所有核心解析器现在都是 Encoding
不可知的,但有些特性不是。例如,内置转换器在进行转换之前会尝试将数据转码为 UTF-8。同样,您可以提供了解您的编码的自定义转换器以避免这种翻译。对于我来说,在 Ruby 的所有编码中支持本机转换实在是太难了。
无论如何,这方面的实际情况很简单:确保传递给 IO
和 String
对象的 CSV
具有正确的 Encoding
设置,一切应该都能正常工作。允许您打开 IO
对象的 CSV
方法(CSV::foreach()
、CSV::open()
、CSV::read()
和 CSV::readlines()
)允许您指定 Encoding
。
当使用与 ASCII 不兼容的 Encoding
将 CSV
生成到 String
时,会产生一个轻微的异常。没有现有的数据供 CSV
用来准备自身,因此您可能需要在大多数情况下手动指定所需的 Encoding
。不过,在使用 CSV::generate_line()
或 Array#to_csv() 时,它将尝试使用输出行中的字段进行猜测。
我尝试在方法文档中指出任何其他 Encoding
问题,因为它们会不断出现。
我已经尽我所能使用 Ruby 附带的所有非“虚拟”编码对本代码进行了测试。但是,这是一段新颖的代码,可能存在一些错误。如果您发现任何问题,请随时 报告。
常量
- ConverterEncoding
所有转换器使用的编码。
- 转换器
包含内置字段转换器的名称和过程的哈希。请参阅 内置字段转换器。
此哈希故意保留未冻结状态,并且可以使用自定义字段转换器进行扩展。请参阅 自定义字段转换器。
- DEFAULT_OPTIONS
方法选项的默认值。
- DateMatcher
- DateTimeMatcher
- FieldInfo
一个
FieldInfo
Struct
包含有关字段在其读取到的数据源中的位置的详细信息。CSV
会将此Struct
传递给根据字段结构做出决策的一些块。有关示例,请参阅CSV.convert_fields()
。index
-
字段在其行中的零基索引。
行
-
此行所在的数据源行。
标题
-
列的标题(如果可用)。
已引用?
-
真或假,原始值是否已引用。
- HeaderConverters
包含内置标题转换器的名称和 Procs 的哈希。请参阅内置标题转换器。
此哈希故意保留为未冻结状态,并且可以使用自定义字段转换器进行扩展。请参阅自定义标题转换器。
- 版本
已安装库的版本。
属性
:call-seq
csv.encoding -> encoding
返回用于解析和生成的编码;请参阅字符编码(M17n 或多语言化)
CSV.new('').encoding # => #<Encoding:UTF-8>
公共类方法
-
从源(String、IO 流或
ARGF
)解析 CSV。 -
使用每个已解析行调用给定的块
-
如果没有标题,则每行都是一个数组。
-
如果有标题,则每行都是一个
CSV::Row
。
-
-
生成 CSV 到输出(String、IO 流或 STDOUT)。
-
返回已解析的源
-
如果没有标题,则返回数组的数组。
-
如果有标题,则返回
CSV::Table
。
-
当给定in_string_or_io
但未给定out_string_or_io
时,从给定的in_string_or_io
解析并生成到 STDOUT。
没有标题的字符串输入
in_string = "foo,0\nbar,1\nbaz,2" CSV.filter(in_string) do |row| row[0].upcase! row[1] = - row[1].to_i end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
输出(到 STDOUT)
FOO,0 BAR,-1 BAZ,-2
带有标题的字符串输入
in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" CSV.filter(in_string, headers: true) do |row| row[0].upcase! row[1] = - row[1].to_i end # => #<CSV::Table mode:col_or_row row_count:4>
输出(到 STDOUT)
Name,Value FOO,0 BAR,-1 BAZ,-2
没有标题的 IO 流输入
File.write('t.csv', "foo,0\nbar,1\nbaz,2") File.open('t.csv') do |in_io| CSV.filter(in_io) do |row| row[0].upcase! row[1] = - row[1].to_i end end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
输出(到 STDOUT)
FOO,0 BAR,-1 BAZ,-2
带有标题的 IO 流输入
File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2") File.open('t.csv') do |in_io| CSV.filter(in_io, headers: true) do |row| row[0].upcase! row[1] = - row[1].to_i end end # => #<CSV::Table mode:col_or_row row_count:4>
输出(到 STDOUT)
Name,Value FOO,0 BAR,-1 BAZ,-2
当in_string_or_io
和out_string_or_io
同时给定时,从in_string_or_io
解析并生成到out_string_or_io
。
没有标题的字符串输出
in_string = "foo,0\nbar,1\nbaz,2" out_string = '' CSV.filter(in_string, out_string) do |row| row[0].upcase! row[1] = - row[1].to_i end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
带有标题的字符串输出
in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" out_string = '' CSV.filter(in_string, out_string, headers: true) do |row| row[0].upcase! row[1] = - row[1].to_i end # => #<CSV::Table mode:col_or_row row_count:4> out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
没有标题的 IO 流输出
in_string = "foo,0\nbar,1\nbaz,2" File.open('t.csv', 'w') do |out_io| CSV.filter(in_string, out_io) do |row| row[0].upcase! row[1] = - row[1].to_i end end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]] File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
带有标题的 IO 流输出
in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2" File.open('t.csv', 'w') do |out_io| CSV.filter(in_string, out_io, headers: true) do |row| row[0].upcase! row[1] = - row[1].to_i end end # => #<CSV::Table mode:col_or_row row_count:4> File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
当既未给定in_string_or_io
也未给定out_string_or_io
时,从ARGF
解析并生成到 STDOUT。
没有标题
# Put Ruby code into a file. ruby = <<-EOT require 'csv' CSV.filter do |row| row[0].upcase! row[1] = - row[1].to_i end EOT File.write('t.rb', ruby) # Put some CSV into a file. File.write('t.csv', "foo,0\nbar,1\nbaz,2") # Run the Ruby code with CSV filename as argument. system(Gem.ruby, "t.rb", "t.csv")
输出(到 STDOUT)
FOO,0 BAR,-1 BAZ,-2
带有标题
# Put Ruby code into a file. ruby = <<-EOT require 'csv' CSV.filter(headers: true) do |row| row[0].upcase! row[1] = - row[1].to_i end EOT File.write('t.rb', ruby) # Put some CSV into a file. File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2") # Run the Ruby code with CSV filename as argument. system(Gem.ruby, "t.rb", "t.csv")
输出(到 STDOUT)
Name,Value FOO,0 BAR,-1 BAZ,-2
参数
-
参数
in_string_or_io
必须是 String 或 IO 流。 -
参数
out_string_or_io
必须是 String 或 IO 流。 -
参数
**options
必须是关键字选项。请参阅解析选项。
# File lib/csv.rb, line 1202 def filter(input=nil, output=nil, **options) # parse options for input, output, or both in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value} options.each do |key, value| case key when /\Ain(?:put)?_(.+)\Z/ in_options[$1.to_sym] = value when /\Aout(?:put)?_(.+)\Z/ out_options[$1.to_sym] = value else in_options[key] = value out_options[key] = value end end # build input and output wrappers input = new(input || ARGF, **in_options) output = new(output || $stdout, **out_options) # process headers need_manual_header_output = (in_options[:headers] and out_options[:headers] == true and out_options[:write_headers]) if need_manual_header_output first_row = input.shift if first_row if first_row.is_a?(Row) headers = first_row.headers yield headers output << headers end yield first_row output << first_row end end # read, yield, write input.each do |row| yield row output << row end end
使用从源path_or_io
读取的每行调用块。
没有标题的路径输入
string = "foo,0\nbar,1\nbaz,2\n" in_path = 't.csv' File.write(in_path, string) CSV.foreach(in_path) {|row| p row }
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
带有标题的路径输入
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" in_path = 't.csv' File.write(in_path, string) CSV.foreach(in_path, headers: true) {|row| p row }
输出
<CSV::Row "Name":"foo" "Value":"0"> <CSV::Row "Name":"bar" "Value":"1"> <CSV::Row "Name":"baz" "Value":"2">
没有标题的 IO 流输入
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) File.open('t.csv') do |in_io| CSV.foreach(in_io) {|row| p row } end
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
带有标题的 IO 流输入
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) File.open('t.csv') do |in_io| CSV.foreach(in_io, headers: true) {|row| p row } end
输出
<CSV::Row "Name":"foo" "Value":"0"> <CSV::Row "Name":"bar" "Value":"1"> <CSV::Row "Name":"baz" "Value":"2">
未给定块时,返回枚举器
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
参数
-
参数
path_or_io
必须是文件路径或 IO 流。 -
参数
mode
(如果已给定)必须是文件模式。请参阅 访问模式。 -
参数
**options
必须是关键字选项。请参阅解析选项。 -
此方法可以选择接受一个附加的
:encoding
选项,你可以使用该选项指定从path
或io
读取的数据的Encoding
。你必须提供此选项,除非你的数据采用Encoding::default_external
给出的编码。解析将使用此选项来确定如何解析数据。你可以提供第二个Encoding
,以便在读取数据时对数据进行转码。例如,encoding: 'UTF-32BE:UTF-8'
将从文件中读取
UTF-32BE
数据,但在解析之前将其转码为UTF-8
。
# File lib/csv.rb, line 1332 def foreach(path, mode="r", **options, &block) return to_enum(__method__, path, mode, **options) unless block_given? open(path, mode, **options) do |csv| csv.each(&block) end end
-
参数
csv_string
(如果已给定)必须是字符串对象;默认为一个新的空字符串。 -
参数
options
(如果已给定)应为生成选项。请参阅 生成选项。
通过 CSV.new(csv_string, **options)
创建新的 CSV 对象;使用 CSV 对象调用块,块可能会修改 CSV 对象;返回从 CSV 对象生成的字符串。
请注意,此方法会修改传递的字符串。如果必须保留字符串,请传递 csv_string
.dup。
此方法有一个附加选项::encoding
,如果未指定 str
,则该选项将设置输出的基本 Encoding
。如果你计划输出与非 ASCII 兼容的数据,则 CSV
需要此提示。
添加行
input_string = "foo,0\nbar,1\nbaz,2\n" output_string = CSV.generate(input_string) do |csv| csv << ['bat', 3] csv << ['bam', 4] end output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" output_string.equal?(input_string) # => true # Same string, modified
将行添加到新字符串中,保留旧字符串
input_string = "foo,0\nbar,1\nbaz,2\n" output_string = CSV.generate(input_string.dup) do |csv| csv << ['bat', 3] csv << ['bam', 4] end output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" input_string # => "foo,0\nbar,1\nbaz,2\n" output_string.equal?(input_string) # => false # Different strings
从无中创建行
output_string = CSV.generate do |csv| csv << ['foo', 0] csv << ['bar', 1] csv << ['baz', 2] end output_string # => "foo,0\nbar,1\nbaz,2\n"
如果 csv_string
不是字符串对象,则引发异常
# Raises TypeError (no implicit conversion of Integer into String) CSV.generate(0)
# File lib/csv.rb, line 1398 def generate(str=nil, **options) encoding = options[:encoding] # add a default empty String, if none was given if str str = StringIO.new(str) str.seek(0, IO::SEEK_END) str.set_encoding(encoding) if encoding else str = +"" str.force_encoding(encoding) if encoding end csv = new(str, **options) # wrap yield csv # yield for appending csv.string # return final String end
返回通过使用指定 options
从 ary
生成 CSV 创建的字符串。
参数 ary
必须是数组。
特殊选项
-
选项
:row_sep
在 Ruby 3.0 或更高版本中默认为"\n">
,在其他版本中默认为<tt>$INPUT_RECORD_SEPARATOR
($/
)。$INPUT_RECORD_SEPARATOR # => "\n"
-
此方法接受一个附加选项
:encoding
,该选项设置输出的基本Encoding
。如果可能,此方法将尝试从row
中的第一个非nil
字段猜测你的Encoding
,但你可能需要将此参数用作备用计划。
有关其他选项
,请参阅生成选项。
返回从数组生成的字符串
CSV.generate_line(['foo', '0']) # => "foo,0\n"
如果ary
不是数组,则引发异常
# Raises NoMethodError (undefined method `find' for :foo:Symbol) CSV.generate_line(:foo)
# File lib/csv.rb, line 1446 def generate_line(row, **options) options = {row_sep: InputRecordSeparator.value}.merge(options) str = +"" if options[:encoding] str.force_encoding(options[:encoding]) else fallback_encoding = nil output_encoding = nil row.each do |field| next unless field.is_a?(String) fallback_encoding ||= field.encoding next if field.ascii_only? output_encoding = field.encoding break end output_encoding ||= fallback_encoding if output_encoding str.force_encoding(output_encoding) end end (new(str, **options) << row).string end
返回使用指定的选项
从 CSV 生成的字符串。
参数rows
必须是行的数组。 Row
是字符串或 CSV::Row 的数组。
特殊选项
-
选项
:row_sep
在 Ruby 3.0 或更高版本上默认为"\n"
,否则默认为$INPUT_RECORD_SEPARATOR
($/
)。$INPUT_RECORD_SEPARATOR # => "\n"
-
此方法接受一个附加选项
:encoding
,该选项设置输出的基本Encoding
。如果可能,此方法将尝试从row
中的第一个非nil
字段猜测你的Encoding
,但你可能需要将此参数用作备用计划。
有关其他选项
,请参阅生成选项。
返回从
CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n"
引发异常
# Raises NoMethodError (undefined method `each' for :foo:Symbol) CSV.generate_lines(:foo)
# File lib/csv.rb, line 1501 def generate_lines(rows, **options) self.generate(**options) do |csv| rows.each do |row| csv << row end end end
创建或检索缓存的 CSV 对象。有关参数和选项,请参阅CSV.new
。
此 API 不支持 Ractor。
如果没有给定块,则返回 CSV 对象。
对instance
的首次调用将创建一个 CSV 对象并将其缓存
s0 = 's0' csv0 = CSV.instance(s0) csv0.class # => CSV
使用相同的string
或io
对instance
的后续调用将检索相同的缓存对象
csv1 = CSV.instance(s0) csv1.class # => CSV csv1.equal?(csv0) # => true # Same CSV object
使用不同的string
或io
对instance
的后续调用将创建一个不同的 CSV 对象并将其缓存。
s1 = 's1' csv2 = CSV.instance(s1) csv2.equal?(csv0) # => false # Different CSV object
所有缓存的对象都保持可用
csv3 = CSV.instance(s0) csv3.equal?(csv0) # true # Same CSV object csv4 = CSV.instance(s1) csv4.equal?(csv2) # true # Same CSV object
当给定一个块时,使用创建或检索的 CSV 对象调用该块;返回块的返回值
CSV.instance(s0) {|csv| :foo } # => :foo
# File lib/csv.rb, line 1006 def instance(data = $stdout, **options) # create a _signature_ for this method call, data object and options sig = [data.object_id] + options.values_at(*DEFAULT_OPTIONS.keys) # fetch or create the instance for this signature @@instances ||= Hash.new instance = (@@instances[sig] ||= new(data, **options)) if block_given? yield instance # run block, if given, returning result else instance # or return the instance end end
返回使用string
或io
和指定的选项
创建的新 CSV 对象。
-
参数
string
应为 String 对象;它将被放入一个新的StringIO
对象中,该对象位于开头。 -
参数
io
应为IO
对象,该对象-
打开以供读取;返回时,
IO
对象将被关闭。 -
位于开头。要定位到末尾以进行追加,请使用
CSV.generate
方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。
-
-
参数
options
:请参阅出于性能原因,选项无法在 CSV 对象中被覆盖,因此此处指定的选项将持续存在。
除了 CSV 实例方法外,还委派了多个 IO 方法。请参阅 委派方法。
从 String 对象创建 CSV 对象
csv = CSV.new('foo,0') csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
从 File 对象创建 CSV 对象
File.write('t.csv', 'foo,0') csv = CSV.new(File.open('t.csv')) csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
如果参数为 nil
,则引发异常
# Raises ArgumentError (Cannot parse nil as CSV): CSV.new(nil)
# File lib/csv.rb, line 1905 def initialize(data, col_sep: ",", row_sep: :auto, quote_char: '"', field_size_limit: nil, max_field_size: nil, converters: nil, unconverted_fields: nil, headers: false, return_headers: false, write_headers: nil, header_converters: nil, skip_blanks: false, force_quotes: false, skip_lines: nil, liberal_parsing: false, internal_encoding: nil, external_encoding: nil, encoding: nil, nil_value: nil, empty_value: "", strip: false, quote_empty: true, write_converters: nil, write_nil_value: nil, write_empty_value: "") raise ArgumentError.new("Cannot parse nil as CSV") if data.nil? if data.is_a?(String) if encoding if encoding.is_a?(String) data_external_encoding, data_internal_encoding = encoding.split(":", 2) if data_internal_encoding data = data.encode(data_internal_encoding, data_external_encoding) else data = data.dup.force_encoding(data_external_encoding) end else data = data.dup.force_encoding(encoding) end end @io = StringIO.new(data) else @io = data end @encoding = determine_encoding(encoding, internal_encoding) @base_fields_converter_options = { nil_value: nil_value, empty_value: empty_value, } @write_fields_converter_options = { nil_value: write_nil_value, empty_value: write_empty_value, } @initial_converters = converters @initial_header_converters = header_converters @initial_write_converters = write_converters if max_field_size.nil? and field_size_limit max_field_size = field_size_limit - 1 end @parser_options = { column_separator: col_sep, row_separator: row_sep, quote_character: quote_char, max_field_size: max_field_size, unconverted_fields: unconverted_fields, headers: headers, return_headers: return_headers, skip_blanks: skip_blanks, skip_lines: skip_lines, liberal_parsing: liberal_parsing, encoding: @encoding, nil_value: nil_value, empty_value: empty_value, strip: strip, } @parser = nil @parser_enumerator = nil @eof_error = nil @writer_options = { encoding: @encoding, force_encoding: (not encoding.nil?), force_quotes: force_quotes, headers: headers, write_headers: write_headers, column_separator: col_sep, row_separator: row_sep, quote_character: quote_char, quote_empty: quote_empty, } @writer = nil writer if @writer_options[:write_headers] end
可能的选项元素
keyword form: :invalid => nil # raise error on invalid byte sequence (default) :invalid => :replace # replace invalid byte sequence :undef => :replace # replace undefined conversion :replace => string # replacement string ("?" or "\uFFFD" if not specified)
-
如果给定参数
path
,它必须是文件的路径。 -
参数
io
应为IO
对象,该对象-
打开以供读取;返回时,
IO
对象将被关闭。 -
位于开头。要定位到末尾以进行追加,请使用
CSV.generate
方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。
-
-
参数
mode
(如果已给定)必须是文件模式。请参阅 访问模式。 -
参数
**options
必须是关键字选项。请参阅 生成选项。 -
此方法可以选择接受一个附加的
:encoding
选项,你可以使用该选项指定从path
或io
读取的数据的Encoding
。你必须提供此选项,除非你的数据采用Encoding::default_external
给出的编码。解析将使用此选项来确定如何解析数据。你可以提供第二个Encoding
,以便在读取数据时对数据进行转码。例如,encoding: 'UTF-32BE:UTF-8'
将从文件中读取
UTF-32BE
数据,但在解析之前将其转码为UTF-8
。
以下示例假定事先执行了
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
如果没有给定块,则返回一个新的 CSV 对象。
使用文件路径创建 CSV 对象
csv = CSV.open(path) csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
使用打开的文件创建 CSV 对象
csv = CSV.open(File.open(path)) csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
如果给定块,则使用创建的 CSV 对象调用该块;返回该块的返回值
使用文件路径
csv = CSV.open(path) {|csv| p csv} csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
输出
#<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
使用打开的文件
csv = CSV.open(File.open(path)) {|csv| p csv} csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
输出
#<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
如果参数不是 String 对象或 IO 对象,则引发异常
# Raises TypeError (no implicit conversion of Symbol into String) CSV.open(:foo)
# File lib/csv.rb, line 1581 def open(filename, mode="r", **options) # wrap a File opened with the remaining +args+ with no newline # decorator file_opts = options.dup unless file_opts.key?(:newline) file_opts[:universal_newline] ||= false end options.delete(:invalid) options.delete(:undef) options.delete(:replace) options.delete_if {|k, _| /newline\z/.match?(k)} begin f = File.open(filename, mode, **file_opts) rescue ArgumentError => e raise unless /needs binmode/.match?(e.message) and mode == "r" mode = "rb" file_opts = {encoding: Encoding.default_external}.merge(file_opts) retry end begin csv = new(f, **options) rescue Exception f.close raise end # handle blocks like Ruby's open(), not like the CSV library if block_given? begin yield csv ensure csv.close end else csv end end
使用指定的 options
解析 string
或 io
。
-
参数
string
应为 String 对象;它将被放入一个新的StringIO
对象中,该对象位于开头。 -
参数
io
应为IO
对象,该对象-
打开以供读取;返回时,
IO
对象将被关闭。 -
位于开头。要定位到末尾以进行追加,请使用
CSV.generate
方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。
-
-
参数
options
:请参阅 解析选项
无选项 headers
¶ ↑
无 {option headers
} 的情况。
以下示例假定事先执行了
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
如果没有给定块,则从源返回一个由数组组成的数组。
解析一个 String
a_of_a = CSV.parse(string) a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
解析一个打开的文件
a_of_a = File.open(path) do |file| CSV.parse(file) end a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
如果给定块,则使用每个已解析的行调用该块
解析一个 String
CSV.parse(string) {|row| p row }
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
解析一个打开的文件
File.open(path) do |file| CSV.parse(file) {|row| p row } end
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
有选项 headers
¶ ↑
带有 {option headers
} 的情况。
以下示例假定事先执行了
string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
如果没有给定代码块,则返回一个由源形成的 CSV::Table
对象。
解析一个 String
csv_table = CSV.parse(string, headers: ['Name', 'Count']) csv_table # => #<CSV::Table mode:col_or_row row_count:5>
解析一个打开的文件
csv_table = File.open(path) do |file| CSV.parse(file, headers: ['Name', 'Count']) end csv_table # => #<CSV::Table mode:col_or_row row_count:4>
如果给定了代码块,则使用每个已解析行调用该代码块,该行已形成为 CSV::Row
对象
解析一个 String
CSV.parse(string, headers: ['Name', 'Count']) {|row| p row }
输出
# <CSV::Row "Name":"foo" "Count":"0"> # <CSV::Row "Name":"bar" "Count":"1"> # <CSV::Row "Name":"baz" "Count":"2">
解析一个打开的文件
File.open(path) do |file| CSV.parse(file, headers: ['Name', 'Count']) {|row| p row } end
输出
# <CSV::Row "Name":"foo" "Count":"0"> # <CSV::Row "Name":"bar" "Count":"1"> # <CSV::Row "Name":"baz" "Count":"2">
如果参数不是 String 对象或 IO 对象,则引发异常
# Raises NoMethodError (undefined method `close' for :foo:Symbol) CSV.parse(:foo)
# File lib/csv.rb, line 1732 def parse(str, **options, &block) csv = new(str, **options) return csv.each(&block) if block_given? # slurp contents, if no block is given begin csv.read ensure csv.close end end
返回通过使用指定的 options
解析 string
或 io
的第一行创建的数据。
-
参数
string
应为 String 对象;它将被放入一个新的StringIO
对象中,该对象位于开头。 -
参数
io
应为IO
对象,该对象-
打开以供读取;返回时,
IO
对象将被关闭。 -
位于开头。要定位到末尾以进行追加,请使用
CSV.generate
方法。对于任何其他定位,请改而传递一个预设的 StringIO 对象。
-
-
参数
options
:请参阅 解析选项
没有 Option headers
¶ ↑
如果没有 headers
选项,则返回第一行作为新的数组。
以下示例假定事先执行了
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
从 String 对象解析第一行
CSV.parse_line(string) # => ["foo", "0"]
从 File
对象解析第一行
File.open(path) do |file| CSV.parse_line(file) # => ["foo", "0"] end # => ["foo", "0"]
如果参数是空 String,则返回 nil
CSV.parse_line('') # => nil
带有 Option headers
¶ ↑
带有 {option headers
},返回第一行作为 CSV::Row
对象。
以下示例假定事先执行了
string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
从 String 对象解析第一行
CSV.parse_line(string, headers: true) # => #<CSV::Row "Name":"foo" "Count":"0">
从 File
对象解析第一行
File.open(path) do |file| CSV.parse_line(file, headers: true) end # => #<CSV::Row "Name":"foo" "Count":"0">
如果参数为 nil
,则引发异常
# Raises ArgumentError (Cannot parse nil as CSV): CSV.parse_line(nil)
# File lib/csv.rb, line 1805 def parse_line(line, **options) new(line, **options).each.first end
使用给定的 options
(参见 CSV.open
)打开给定的 source
,读取源(参见 CSV#read
),并返回结果,结果将是数组的数组或 CSV::Table
。
没有标题
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
带有标题
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) CSV.read(path, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
# File lib/csv.rb, line 1829 def read(path, **options) open(path, **options) { |csv| csv.read } end
别名 CSV.read
。
# File lib/csv.rb, line 1837 def readlines(path, **options) read(path, **options) end
使用 source
、options
和某些默认选项调用 CSV.read
-
headers
:true
-
converters
::numeric
-
header_converters
::symbol
返回 CSV::Table
对象。
示例
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:4>
# File lib/csv.rb, line 1856 def table(path, **options) default_options = { headers: true, converters: :numeric, header_converters: :symbol, } options = default_options.merge(options) read(path, **options) end
公共实例方法
将一行追加到 self
。
-
参数
row
必须是 Array 对象或CSV::Row
对象。 -
输出流必须打开用于写入。
追加数组
CSV.generate do |csv| csv << ['foo', 0] csv << ['bar', 1] csv << ['baz', 2] end # => "foo,0\nbar,1\nbaz,2\n"
追加 CSV::Rows
headers = [] CSV.generate do |csv| csv << CSV::Row.new(headers, ['foo', 0]) csv << CSV::Row.new(headers, ['bar', 1]) csv << CSV::Row.new(headers, ['baz', 2]) end # => "foo,0\nbar,1\nbaz,2\n"
CSV::Row
对象中的标题不会被追加
headers = ['Name', 'Count'] CSV.generate do |csv| csv << CSV::Row.new(headers, ['foo', 0]) csv << CSV::Row.new(headers, ['bar', 1]) csv << CSV::Row.new(headers, ['baz', 2]) end # => "foo,0\nbar,1\nbaz,2\n"
如果row
不是数组或 CSV::Row,则引发异常
CSV.generate do |csv| # Raises NoMethodError (undefined method `collect' for :foo:Symbol) csv << :foo end
如果输出流未打开以进行写入,则引发异常
path = 't.csv' File.write(path, '') File.open(path) do |file| CSV.open(file) do |csv| # Raises IOError (not opened for writing) csv << ['foo', 0] end end
# File lib/csv.rb, line 2372 def <<(row) writer << row self end
# File lib/csv.rb, line 2261 def binmode? if @io.respond_to?(:binmode?) @io.binmode? else false end end
返回编码的列分隔符;用于解析和写入;请参见{Option col_sep
}
CSV.new('').col_sep # => ","
# File lib/csv.rb, line 2009 def col_sep parser.column_separator end
-
如果没有块,则安装字段转换器(Proc)。
-
使用块,定义并安装自定义字段转换器。
-
返回已安装字段转换器的数组。
-
如果给出了参数
converter_name
,则它应该是现有字段转换器的名称。
请参阅 字段转换器。
如果没有块,则安装字段转换器
csv = CSV.new('') csv.convert(:integer) csv.convert(:float) csv.convert(:date) csv.converters # => [:integer, :float, :date]
如果给出了块,则对每个字段调用该块
-
参数
field
是字段值。 -
参数
field_info
是一个CSV::FieldInfo
对象,其中包含有关该字段的详细信息。
此处的示例假定先前执行了
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string)
给出一个块的示例
csv = CSV.open(path) csv.convert {|field, field_info| p [field, field_info]; field.upcase } csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]]
输出
["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>] ["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>] ["bar", #<struct CSV::FieldInfo index=0, line=2, header=nil>] ["1", #<struct CSV::FieldInfo index=1, line=2, header=nil>] ["baz", #<struct CSV::FieldInfo index=0, line=3, header=nil>] ["2", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
该块不必返回 String 对象
csv = CSV.open(path) csv.convert {|field, field_info| field.to_sym } csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]]
如果给出了converter_name
,则不会调用该块
csv = CSV.open(path) csv.convert(:integer) {|field, field_info| fail 'Cannot happen' } csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
如果converter_name
不是内置字段转换器的名称,则引发解析时异常
csv = CSV.open(path) csv.convert(:nosuch) => [nil] # Raises NoMethodError (undefined method `arity' for nil:NilClass) csv.read
# File lib/csv.rb, line 2443 def convert(name = nil, &converter) parser_fields_converter.add_converter(name, &converter) end
返回包含字段转换器的数组;请参见字段转换器
csv = CSV.new('') csv.converters # => [] csv.convert(:integer) csv.converters # => [:integer] csv.convert(proc {|x| x.to_s }) csv.converters
请注意,您需要在主Ractor
上调用+Ractor.make_shareable(CSV::Converters
)+才能使用此方法。
# File lib/csv.rb, line 2082 def converters parser_fields_converter.map do |converter| name = Converters.rassoc(converter) name ? name.first : converter end end
使用每一行调用该块。数据源必须打开以进行读取。
没有标题
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.each do |row| p row end
输出
["foo", "0"] ["bar", "1"] ["baz", "2"]
带有标题
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" csv = CSV.new(string, headers: true) csv.each do |row| p row end
输出
<CSV::Row "Name":"foo" "Value":"0"> <CSV::Row "Name":"bar" "Value":"1"> <CSV::Row "Name":"baz" "Value":"2">
如果源未打开以进行读取,则引发异常
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.close # Raises IOError (not opened for reading) csv.each do |row| p row end
# File lib/csv.rb, line 2554 def each(&block) return to_enum(__method__) unless block_given? begin while true yield(parser_enumerator.next) end rescue StopIteration end end
# File lib/csv.rb, line 2297 def eof? return false if @eof_error begin parser_enumerator.peek false rescue MalformedCSVError => error @eof_error = error false rescue StopIteration true end end
返回字段大小限制;用于解析;参见 {Option field_size_limit
}
CSV.new('').field_size_limit # => nil
自 3.2.3 起已弃用。请改用 max_field_size
。
# File lib/csv.rb, line 2041 def field_size_limit parser.field_size_limit end
# File lib/csv.rb, line 2269 def flock(*args) raise NotImplementedError unless @io.respond_to?(:flock) @io.flock(*args) end
返回一个值,该值决定是否对所有输出字段加上引号;用于生成;参见 {Option force_quotes
}
CSV.new('').force_quotes? # => false
# File lib/csv.rb, line 2172 def force_quotes? @writer_options[:force_quotes] end
该块不必返回 String 对象
csv = CSV.open(path, headers: true) csv.header_convert {|header, field_info| header.to_sym } table = csv.read table.headers # => [:Name, :Value]
如果给出了converter_name
,则不会调用该块
csv = CSV.open(path, headers: true) csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' } table = csv.read table.headers # => ["name", "value"]
如果converter_name
不是内置字段转换器的名称,则引发解析时异常
csv = CSV.open(path, headers: true) csv.header_convert(:nosuch) # Raises NoMethodError (undefined method `arity' for nil:NilClass) csv.read
# File lib/csv.rb, line 2509 def header_convert(name = nil, &converter) header_fields_converter.add_converter(name, &converter) end
返回一个包含标题转换器的数组;用于解析;参见 标题转换器
CSV.new('').header_converters # => []
请注意,您需要在主 Ractor
上调用 +Ractor.make_shareable(CSV::HeaderConverters
)+ 才能使用此方法。
# File lib/csv.rb, line 2148 def header_converters header_fields_converter.map do |converter| name = HeaderConverters.rassoc(converter) name ? name.first : converter end end
如果要读取的下一行是标题行,则返回 true
;否则返回 false
。
没有标题
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.header_row? # => false
带有标题
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" csv = CSV.new(string, headers: true) csv.header_row? # => true csv.shift # => #<CSV::Row "Name":"foo" "Value":"0"> csv.header_row? # => false
如果源未打开以进行读取,则引发异常
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.close # Raises IOError (not opened for reading) csv.header_row?
# File lib/csv.rb, line 2631 def header_row? parser.header_row? end
返回一个值,该值决定是否使用标题;用于解析;参见 {Option headers
}
CSV.new('').headers # => nil
# File lib/csv.rb, line 2106 def headers if @writer @writer.headers else parsed_headers = parser.headers return parsed_headers if parsed_headers raw_headers = @parser_options[:headers] raw_headers = nil if raw_headers == false raw_headers end end
返回一个字符串,显示 self
的某些属性
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" csv = CSV.new(string, headers: true) s = csv.inspect s # => "#<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:\",\" row_sep:\"\\n\" quote_char:\"\\\"\" headers:true>"
# File lib/csv.rb, line 2690 def inspect str = ["#<", self.class.to_s, " io_type:"] # show type of wrapped IO if @io == $stdout then str << "$stdout" elsif @io == $stdin then str << "$stdin" elsif @io == $stderr then str << "$stderr" else str << @io.class.to_s end # show IO.path(), if available if @io.respond_to?(:path) and (p = @io.path) str << " io_path:" << p.inspect end # show encoding str << " encoding:" << @encoding.name # show other attributes ["lineno", "col_sep", "row_sep", "quote_char"].each do |attr_name| if a = __send__(attr_name) str << " " << attr_name << ":" << a.inspect end end ["skip_blanks", "liberal_parsing"].each do |attr_name| if a = __send__("#{attr_name}?") str << " " << attr_name << ":" << a.inspect end end _headers = headers str << " headers:" << _headers.inspect if _headers str << ">" begin str.join('') rescue # any encoding error str.map do |s| e = Encoding::Converter.asciicompat_encoding(s.encoding) e ? s.encode(e) : s.force_encoding("ASCII-8BIT") end.join('') end end
# File lib/csv.rb, line 2274 def ioctl(*args) raise NotImplementedError unless @io.respond_to?(:ioctl) @io.ioctl(*args) end
返回一个值,该值决定是否处理非法输入;用于解析;参见 {Option liberal_parsing
}
CSV.new('').liberal_parsing? # => false
# File lib/csv.rb, line 2182 def liberal_parsing? parser.liberal_parsing? end
返回最近读取的行
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) CSV.open(path) do |csv| csv.each do |row| p [csv.lineno, csv.line] end end
输出
[1, "foo,0\n"] [2, "bar,1\n"] [3, "baz,2\n"]
# File lib/csv.rb, line 2247 def line parser.line end
返回已解析或生成的行的计数。
解析
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) CSV.open(path) do |csv| csv.each do |row| p [csv.lineno, row] end end
输出
[1, ["foo", "0"]] [2, ["bar", "1"]] [3, ["baz", "2"]]
生成
CSV.generate do |csv| p csv.lineno; csv << ['foo', 0] p csv.lineno; csv << ['bar', 1] p csv.lineno; csv << ['baz', 2] end
输出
0 1 2
# File lib/csv.rb, line 2223 def lineno if @writer @writer.lineno else parser.lineno end end
返回字段大小限制;用于解析;参见 {Option max_field_size
}
CSV.new('').max_field_size # => nil
自 3.2.3 起。
# File lib/csv.rb, line 2053 def max_field_size parser.max_field_size end
# File lib/csv.rb, line 2279 def path @io.path if @io.respond_to?(:path) end
返回编码的引用字符;用于解析和写入;参见 {Option quote_char
}
CSV.new('').quote_char # => "\""
# File lib/csv.rb, line 2029 def quote_char parser.quote_character end
将 self
中的剩余行组成
-
如果使用标题,则为
CSV::Table
对象。 -
否则为数组的数组。
数据源必须打开才能读取。
没有标题
string = "foo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) csv = CSV.open(path) csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
带有标题
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" path = 't.csv' File.write(path, string) csv = CSV.open(path, headers: true) csv.read # => #<CSV::Table mode:col_or_row row_count:4>
如果源未打开以进行读取,则引发异常
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.close # Raises IOError (not opened for reading) csv.read
# File lib/csv.rb, line 2595 def read rows = to_a if parser.use_headers? Table.new(rows, headers: parser.headers) else rows end end
返回确定是否返回标题的值;用于解析;参见 {Option return_headers
}
CSV.new('').return_headers? # => false
# File lib/csv.rb, line 2124 def return_headers? parser.return_headers? end
倒带底层 IO
对象并重置 CSV 的 lineno() 计数器。
# File lib/csv.rb, line 2312 def rewind @parser = nil @parser_enumerator = nil @eof_error = nil @writer.rewind if @writer @io.rewind end
返回编码的行分隔符;用于解析和写入;参见 {Option row_sep
}
CSV.new('').row_sep # => "\n"
# File lib/csv.rb, line 2019 def row_sep parser.row_separator end
返回下一行数据,形式为
-
如果不使用标题,则为数组。
-
如果使用标题,则为
CSV::Row
对象。
数据源必须打开才能读取。
没有标题
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.shift # => ["foo", "0"] csv.shift # => ["bar", "1"] csv.shift # => ["baz", "2"] csv.shift # => nil
带有标题
string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" csv = CSV.new(string, headers: true) csv.shift # => #<CSV::Row "Name":"foo" "Value":"0"> csv.shift # => #<CSV::Row "Name":"bar" "Value":"1"> csv.shift # => #<CSV::Row "Name":"baz" "Value":"2"> csv.shift # => nil
如果源未打开以进行读取,则引发异常
string = "foo,0\nbar,1\nbaz,2\n" csv = CSV.new(string) csv.close # Raises IOError (not opened for reading) csv.shift
# File lib/csv.rb, line 2668 def shift if @eof_error eof_error, @eof_error = @eof_error, nil raise eof_error end begin parser_enumerator.next rescue StopIteration nil end end
返回一个值,该值确定是否忽略空行;用于解析;参见 {Option skip_blanks
}
CSV.new('').skip_blanks? # => false
# File lib/csv.rb, line 2161 def skip_blanks? parser.skip_blanks? end
返回用于识别注释行的正则表达式;用于解析;参见 {Option skip_lines
}
CSV.new('').skip_lines # => nil
# File lib/csv.rb, line 2063 def skip_lines parser.skip_lines end
# File lib/csv.rb, line 2283 def stat(*args) raise NotImplementedError unless @io.respond_to?(:stat) @io.stat(*args) end
# File lib/csv.rb, line 2288 def to_i raise NotImplementedError unless @io.respond_to?(:to_i) @io.to_i end
# File lib/csv.rb, line 2293 def to_io @io.respond_to?(:to_io) ? @io.to_io : @io end
返回一个值,该值确定是否提供未转换的字段;用于解析;参见 {Option unconverted_fields
}
CSV.new('').unconverted_fields? # => nil
# File lib/csv.rb, line 2096 def unconverted_fields? parser.unconverted_fields? end
返回一个值,该值确定是否写入标题;用于生成;参见 {Option write_headers
}
CSV.new('').write_headers? # => nil
# File lib/csv.rb, line 2134 def write_headers? @writer_options[:write_headers] end
私有实例方法
# File lib/csv.rb, line 2822 def build_fields_converter(initial_converters, options) fields_converter = FieldsConverter.new(options) normalize_converters(initial_converters).each do |name, converter| fields_converter.add_converter(name, &converter) end fields_converter end
# File lib/csv.rb, line 2804 def build_header_fields_converter specific_options = { builtin_converters_name: :HeaderConverters, accept_nil: true, } options = @base_fields_converter_options.merge(specific_options) build_fields_converter(@initial_header_converters, options) end
# File lib/csv.rb, line 2792 def build_parser_fields_converter specific_options = { builtin_converters_name: :Converters, } options = @base_fields_converter_options.merge(specific_options) build_fields_converter(@initial_converters, options) end
# File lib/csv.rb, line 2817 def build_writer_fields_converter build_fields_converter(@initial_write_converters, @write_fields_converter_options) end
使用 @converters
处理 fields
,如果将 headers
作为 true
传递,则使用 @header_converters
,返回转换后的字段集。将字段更改为 String
以外内容的任何转换器都会停止该字段的转换管道。这主要是一种效率捷径。
# File lib/csv.rb, line 2767 def convert_fields(fields, headers = false) if headers header_fields_converter.convert(fields, nil, 0) else parser_fields_converter.convert(fields, @headers, lineno) end end
# File lib/csv.rb, line 2730 def determine_encoding(encoding, internal_encoding) # honor the IO encoding if we can, otherwise default to ASCII-8BIT io_encoding = raw_encoding return io_encoding if io_encoding return Encoding.find(internal_encoding) if internal_encoding if encoding encoding, = encoding.split(":", 2) if encoding.is_a?(String) return Encoding.find(encoding) end Encoding.default_internal || Encoding.default_external end
# File lib/csv.rb, line 2800 def header_fields_converter @header_fields_converter ||= build_header_fields_converter end
# File lib/csv.rb, line 2745 def normalize_converters(converters) converters ||= [] unless converters.is_a?(Array) converters = [converters] end converters.collect do |converter| case converter when Proc # custom code block [nil, converter] else # by name [converter, nil] end end end
# File lib/csv.rb, line 2830 def parser @parser ||= Parser.new(@io, parser_options) end
# File lib/csv.rb, line 2839 def parser_enumerator @parser_enumerator ||= parser.parse end
# File lib/csv.rb, line 2788 def parser_fields_converter @parser_fields_converter ||= build_parser_fields_converter end
# File lib/csv.rb, line 2834 def parser_options @parser_options.merge(header_fields_converter: header_fields_converter, fields_converter: parser_fields_converter) end
返回内部 IO
对象的编码。
# File lib/csv.rb, line 2778 def raw_encoding if @io.respond_to? :internal_encoding @io.internal_encoding || @io.external_encoding elsif @io.respond_to? :encoding @io.encoding else nil end end
# File lib/csv.rb, line 2843 def writer @writer ||= Writer.new(@io, writer_options) end
# File lib/csv.rb, line 2813 def writer_fields_converter @writer_fields_converter ||= build_writer_fields_converter end
# File lib/csv.rb, line 2847 def writer_options @writer_options.merge(header_fields_converter: header_fields_converter, fields_converter: writer_fields_converter) end