我正在使用Ruby的内置CSV生成一些CSV输出.一切正常,但客户希望输出中的name字段包含双引号,因此输出看起来像输入文件.例如,输入看起来像这样:
1,1.1.1.1,"Firstname Lastname",more,fields
2,2.2.2.2,"Firstname Lastname, Jr.",more,fields
Run Code Online (Sandbox Code Playgroud)
CSV的输出是正确的,如下所示:
1,1.1.1.1,Firstname Lastname,more,fields
2,2.2.2.2,"Firstname Lastname, Jr.",more,fields
Run Code Online (Sandbox Code Playgroud)
我知道CSV正在做正确的事情,因为它没有引用第三个字段只是因为它嵌入了空格,并且当它有嵌入的逗号时用双引号包装字段.为了帮助客户感到温暖和模糊,我想做的是告诉CSV总是双引号第三个字段.
我尝试在我的to_a方法中用双引号包装该字段,这会创建一个"Firstname Lastname"传递给CSV 的字段,但是CSV嘲笑我的微不足道的尝试和输出"""Firstname Lastname""".这是正确的事情,因为它正在逃避双引号,所以这不起作用.
然后我尝试:force_quotes => true在open方法中设置CSV ,输出双引号按预期包装所有字段,但客户不喜欢,我也期望.所以,这也没有用.
我查看了Table和Row文档,似乎没有任何内容可以让我访问"生成字符串字段"方法,或者设置"for field n always use quoting"标志的方法.
我即将深入了解消息来源,看看是否有一些超级秘密的调整,或者是否有办法修补CSV并弯曲它以实现我的意愿,但想知道是否有人有一些特殊知识或者遇到过这个问题之前.
而且,是的,我知道我可以滚动自己的CSV输出,但我更喜欢不重新发明经过良好测试的轮子.而且,我也知道FasterCSV; 这是我正在使用的Ruby 1.9.2的一部分,因此明确使用FasterCSV并没有什么特别之处.另外,我没有使用Rails并且无意在Rails中重写它,所以除非你有一个可爱的方式使用一小部分Rails实现它,所以不要打扰.我会向你推荐任何使用这些方法的建议,因为你没有费心去读这篇文章.
好吧,有一种方法可以做到,但它并不像我希望CSV代码允许的那样干净.
我必须继承CSV,然后重写CSV::Row.<<=方法并添加另一个方法forced_quote_fields=,以便可以定义我想强制引用的字段,再从其他方法中拉出两个lambdas.至少它适用于我想要的东西:
require 'csv'
class MyCSV < CSV
def <<(row)
# make sure headers have been assigned
if header_row? and [Array, String].include? @use_headers.class
parse_headers # won't read data for Array or String
self << @headers if @write_headers
end
# handle CSV::Row objects and Hashes
row = case row
when self.class::Row then row.fields
when Hash then @headers.map { |header| row[header] }
else row
end
@headers = row if header_row?
@lineno += 1
@do_quote ||= lambda do |field|
field = String(field)
encoded_quote = @quote_char.encode(field.encoding)
encoded_quote +
field.gsub(encoded_quote, encoded_quote * 2) +
encoded_quote
end
@quotable_chars ||= encode_str("\r\n", @col_sep, @quote_char)
@forced_quote_fields ||= []
@my_quote_lambda ||= lambda do |field, index|
if field.nil? # represent +nil+ fields as empty unquoted fields
""
else
field = String(field) # Stringify fields
# represent empty fields as empty quoted fields
if (
field.empty? or
field.count(@quotable_chars).nonzero? or
@forced_quote_fields.include?(index)
)
@do_quote.call(field)
else
field # unquoted field
end
end
end
output = row.map.with_index(&@my_quote_lambda).join(@col_sep) + @row_sep # quote and separate
if (
@io.is_a?(StringIO) and
output.encoding != raw_encoding and
(compatible_encoding = Encoding.compatible?(@io.string, output))
)
@io = StringIO.new(@io.string.force_encoding(compatible_encoding))
@io.seek(0, IO::SEEK_END)
end
@io << output
self # for chaining
end
alias_method :add_row, :<<
alias_method :puts, :<<
def forced_quote_fields=(indexes=[])
@forced_quote_fields = indexes
end
end
Run Code Online (Sandbox Code Playgroud)
这就是代码.打电话给:
data = [
%w[1 2 3],
[ 2, 'two too', 3 ],
[ 3, 'two, too', 3 ]
]
quote_fields = [1]
puts "Ruby version: #{ RUBY_VERSION }"
puts "Quoting fields: #{ quote_fields.join(', ') }", "\n"
csv = MyCSV.generate do |_csv|
_csv.forced_quote_fields = quote_fields
data.each do |d|
_csv << d
end
end
puts csv
Run Code Online (Sandbox Code Playgroud)
结果是:
# >> Ruby version: 1.9.2
# >> Quoting fields: 1
# >>
# >> 1,"2",3
# >> 2,"two too",3
# >> 3,"two, too",3
Run Code Online (Sandbox Code Playgroud)
这篇文章很老,但我无法相信没有人想到这一点.
为什么不这样做:
csv = CSV.generate :quote_char => "\0" do |csv|
Run Code Online (Sandbox Code Playgroud)
其中\ 0是一个空字符,然后只需在每个需要它们的字段中添加引号:
csv << [product.upc, "\"" + product.name + "\"" # ...
Run Code Online (Sandbox Code Playgroud)
然后在最后你可以做一个
csv.gsub!(/\0/, '')
Run Code Online (Sandbox Code Playgroud)
我怀疑这是否会帮助客户在这么长时间后感觉温暖和模糊,但这似乎有效:
require 'csv'
#prepare a lambda which converts field with index 2
quote_col2 = lambda do |field, fieldinfo|
# fieldinfo has a line- ,header- and index-method
if fieldinfo.index == 2 && !field.start_with?('"') then
'"' + field + '"'
else
field
end
end
# specify above lambda as one of the converters
csv = CSV.read("test1.csv", :converters => [quote_col2])
p csv
# => [["aaa", "bbb", "\"ccc\"", "ddd"], ["fff", "ggg", "\"hhh\"", "iii"]]
File.open("test1.txt","w"){|out| csv.each{|line|out.puts line.join(",")}}
Run Code Online (Sandbox Code Playgroud)
CSV有一个force_quotes选项将强制它引用所有字段(当您最初发布此内容时它可能不存在)。我意识到这并不完全是你所提议的,但它不是猴子修补。
2.1.0 :008 > puts CSV.generate_line [1,'1.1.1.1','Firstname Lastname','more','fields']
1,1.1.1.1,Firstname Lastname,more,fields
2.1.0 :009 > puts CSV.generate_line [1,'1.1.1.1','Firstname Lastname','more','fields'], force_quotes: true
"1","1.1.1.1","Firstname Lastname","more","fields"
Run Code Online (Sandbox Code Playgroud)
缺点是第一个整数值最终以字符串形式列出,这在导入 Excel 时会发生变化。
| 归档时间: |
|
| 查看次数: |
9358 次 |
| 最近记录: |