当前位置: 首页>>代码示例 >>用法及示例精选 >>正文


Ruby CSV类用法及代码示例


本文简要介绍ruby语言中 CSV类 的用法。

CSV

CSV(逗号分隔变量)数据是表格的文本表示:

  • row separator 分隔表行。常见的行分隔符是换行符 "\n"

  • column separator 分隔一行中的字段。常见的列分隔符是逗号字符 ","

此 CSV 字符串带有行分隔符 "\n" 和列分隔符 "," ,具有三行两列:

"foo,0\nbar,1\nbaz,2\n"

尽管名称为 CSV,但 CSV 表示可以使用不同的分隔符。

有关表格的更多信息,请参阅 Wikipedia 文章“Table (information)”,尤其是其部分“Simple table

CSV 类

Class CSV 提供以下方法:

  • 从字符串对象、文件(通过其文件路径)或 IO 对象解析 CSV 数据。

  • 将 CSV 数据生成到 String 对象。

要使 CSV 可用:

require 'csv'

这里的所有示例都假设这已经完成。

保持简单

一个 CSV 对象有几十个实例方法,它们提供对解析和生成 CSV 数据的细粒度控制。但是,对于许多需求,更简单的方法就可以了。

本节总结了 CSV 中的单例方法,这些方法允许您在不显式创建 CSV 对象的情况下进行解析和生成。有关详细信息,请点击链接。

简单解析

解析方法通常返回以下之一:

  • 一个字符串数组:

    • 外部数组是整个“table”。

    • 每个内部数组是一行。

    • 每个字符串都是一个字段。

  • 一个 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"

“Filtering” CSV

方法 CSV.filter 为 CSV 数据提供了一个 Unix-style 过滤器。处理输入数据以形成输出数据:

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 中有包装 “guard code”。)您可以调用:

选项

选项的默认值为:

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 :指定允许的最大字段大小。

  • converters :指定要使用的字段转换器。

  • unconverted_fields :指定未转换的字段是否可用。

  • headers :指定数据是否包含标头,或指定标头本身。

  • return_headers :指定是否要返回标头。

  • header_converters :指定要使用的标头转换器。

  • skip_blanks :指定是否忽略空白行。

  • skip_lines :指定如何识别注释行。

  • strip :指定是否要从字段中去除前导和尾随空格。这必须与 col_sep 兼容;如果不是,则会引发 ArgumentError 异常。

  • liberal_parsing :指定 CSV 是否应尝试解析不合规的数据。

  • nil_value :指定要替换每个空 (no-text) 字段的对象。

  • 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 ( $/ ):

  • 没有找到这些序列。

  • 数据为 ARGFSTDINSTDOUTSTDERR

  • 该流仅可用于输出。

显然,发现需要一点时间。如果速度很重要,请手动 Set 。另请注意, IO 对象应在 Windows 上以二进制模式打开,如果此函数将用于 line-ending 翻译可能会导致将文档位置重置为预读之前的位置时出现问题。

如果给定值不是String-convertible,则引发异常:

row_sep = BasicObject.new
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.generate(ary, row_sep: row_sep)
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.parse(str, row_sep: row_sep)
选项col_sep

指定用于解析和生成的字符串字段分隔符。 String 在使用前会被转码为数据的 Encoding。

默认值:

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)

如果给定值不是String-convertible,则引发异常:

col_sep = BasicObject.new
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.generate(line, col_sep: col_sep)
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.parse(str, col_sep: col_sep)
选项quote_char

指定用于在解析和生成中引用字段的字符(长度为 1 的字符串)。这个 String 会在使用前转码成数据的Encoding。

默认值:

CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)

这对于错误地使用 ' (single-quote) 而不是正确的 " (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"]]

使用' (single-quote):

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 执行任何转换之前在源数据中找到的字段值。

When option unconverted_fields is true , each returned row (Array or CSV::Row) has an added method, unconverted_fields , that returns the unconverted field values:

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">

如果设置为 Array,则 Array 元素被视为标题:

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">

如果设置为 String str ,则使用当前 options 调用方法 CSV::parse_line(str, options) ,并将返回的 Array 视为标头:

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

函数与选项转换器相同,不同之处在于:

  • 转换器仅适用于标题行。

  • 内置的标头转换器是 :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

另请参见选项跳过线。

对于本节中的示例:

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"]]

如果给定的对象不是 Regexp、String 或 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"]
选项nil_value

指定要替换每个空 (no-text) 字段的对象。

默认值:

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 ( $/ ):

  • 没有找到这些序列。

  • 数据为 ARGFSTDINSTDOUTSTDERR

  • 该流仅可用于输出。

显然,发现需要一点时间。如果速度很重要,请手动 Set 。另请注意, IO 对象应在 Windows 上以二进制模式打开,如果此函数将用于 line-ending 翻译可能会导致将文档位置重置为预读之前的位置时出现问题。

如果给定值不是String-convertible,则引发异常:

row_sep = BasicObject.new
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.generate(ary, row_sep: row_sep)
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.parse(str, row_sep: row_sep)
选项col_sep

指定用于解析和生成的字符串字段分隔符。 String 在使用前会被转码为数据的 Encoding。

默认值:

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)

如果给定值不是String-convertible,则引发异常:

col_sep = BasicObject.new
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.generate(line, col_sep: col_sep)
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
CSV.parse(str, col_sep: col_sep)
选项quote_char

指定用于在解析和生成中引用字段的字符(长度为 1 的字符串)。这个 String 会在使用前转码成数据的Encoding。

默认值:

CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)

这对于错误地使用 ' (single-quote) 而不是正确的 " (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"]]

使用' (single-quote):

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"

另请参阅写入转换器

如果转换器返回的值既不是nil 也不是String-convertible,则引发异常:

bad_converter = proc {|field| BasicObject.new }
# Raises NoMethodError (undefined method `is_a?' for #<BasicObject:>)
CSV.generate_line(['a', 'b', 'c'], write_converters: bad_converter)#
选项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 解析的每个值(字段或标题)都形成一个字符串。您可以使用 field converterheader converter 来拦截和修改解析的值:

  • 请参阅现场转换器。

  • 请参阅标头转换器。

同样默认情况下,生成期间要写入的每个值都是“按原样”写入的。您可以使用 write converter 在写入之前修改值。

  • 请参阅写入转换器。

指定转换器

您可以在各种 CSV 方法的 options 参数中指定用于解析或生成的转换器:

  • 用于转换解析的字段值的选项converters

  • 选项 header_converters 用于转换已解析的标头值。

  • 选项write_converters 用于转换要写入(生成)的值。

指定转换器的三种形式:

  • 转换器过程:用于转换的可执行代码。

  • 转换器名称:存储的转换器的名称。

  • 转换器列表:转换器过程、转换器名称和转换器列表的数组。

转换器过程

此转换器过程 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 。它有几个内置的转换器过程:

  • :integer :将每个 String-embedded 整数转换为真正的整数。

  • :float :将每个 String-embedded 浮点数转换为真正的浮点数。

  • :date :将每个 String-embedded 日期转换为真实日期。

  • :date_time :将每个 String-embedded 日期时间转换为真正的 DateTime

.此示例创建一个转换器 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"]]

标头转换器的存储结构是 Hash CSV::HeaderConverters ,其工作方式相同。它还具有内置的转换器过程:

  • :downcase :缩小每个标题。

  • :symbol :将每个标头转换为符号。

写头没有这样的存储结构。

为了使解析方法能够访问存储在非主 Ractor 中的转换器,必须首先使存储结构可共享。因此,必须在创建使用存储在这些结构中的转换器的 Ractor 之前调用 Ractor.make_shareable(CSV::Converters)Ractor.make_shareable(CSV::HeaderConverters)。 (由于使存储结构可共享涉及冻结它们,因此必须首先添加要使用的任何自定义转换器。)

转换器列表

converter list 是一个数组,可以包括以下任何种类:

  • 转换器过程。

  • 存储的转换器的名称。

  • 嵌套转换器列表。

例子:

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 所做的。)

有三种方法可以使用场转换器。

  • 使用带有解析方法的选项转换器:

    ary = CSV.parse(string, converters: :integer)
    ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]]
  • 将选项转换器与新的 CSV 实例一起使用:

    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]]

安装字段转换器不会影响 already-read 行:

csv = CSV.new(string)
csv.shift # => ["foo", "0"]
# Add a converter.
csv.convert(:integer)
csv.converters # => [:integer]
csv.read # => [["bar", 1], ["baz", 2]]

有额外的内置转换器,也支持自定义转换器。

内置场转换器

内置字段转换器在 Hash CSV::Converters 中:

  • 每个键都是一个字段转换器名称。

  • 每个值是以下之一:

    • 一个 Proc 字段转换器。

    • 字段转换器名称的数组。

展示:

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"]]

您可以在 Converters 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"]]

标头转换器

标头转换器仅在标头上运行(而不在其他行上)。

使用标头转换器有三种方法;这些示例使用内置的标头转换器 :dowhcase ,它将每个已解析的标头小写。

  • 带有单例解析方法的选项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"]
  • Method 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"]

Converter :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 编码中支持原生转换对我来说太难了。

无论如何,实际操作很简单:确保传递给 CSV IO String 对象具有正确的 Encoding 设置,并且一切都应该正常工作。 CSV 方法允许您打开 IO 对象( CSV::foreach() CSV::open() CSV::read() CSV::readlines() )允许您指定 Encoding

CSV 生成为 String 时出现一个小例外,其中 Encoding 与ASCII 不兼容。 CSV 没有可用于自行准备的现有数据,因此在大多数情况下,您可能需要手动指定所需的 Encoding 。但是,当使用 CSV::generate_line() 或 Array#to_csv() 时,它会尝试使用输出行中的字段进行猜测。

我尝试在方法文档中指出任何其他 Encoding 问题。

已经尽我所能对 Ruby 附带的所有非“dummy” 编码进行了测试。然而,它是勇敢的新代码,可能有一些错误。如果您发现任何问题,请随时report

相关用法


注:本文由纯净天空筛选整理自ruby-lang.org大神的英文原创作品 CSV类。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。