我正在使用 Ruby 的内置 CSV 生成一些 CSV 输出。一切正常,但客户希望输出中的名称字段包含双引号,以便输出看起来像输入文件。例如,输入看起来像这样:
1,1.1.1.1,"Firstname Lastname",more,fields
2,2.2.2.2,"Firstname Lastname, Jr.",more,fields
CSV 的正确输出如下所示:
1,1.1.1.1,Firstname Lastname,more,fields
2,2.2.2.2,"Firstname Lastname, Jr.",more,fields
我知道 CSV 的做法是正确的,因为它没有双引号第三个字段,只是因为它嵌入了空格,并且在该字段包含嵌入的逗号时用双引号引起来。为了帮助客户感到温暖和模糊,我想做的是告诉 CSV 始终双引号第三个字段。
我尝试在我的 to_a
方法中将字段用双引号引起来,这会创建一个 "Firstname Lastname"
字段并传递给 CSV,但 CSV mock 我的弱小-人类尝试并输出 """Firstname Lastname"""
。这是正确的做法,因为它转义了双引号,所以没有用。
然后我尝试在 open
方法中设置 CSV 的 :force_quotes => true
,它按预期输出双引号包裹所有字段,但客户不喜欢那,这也是我所期望的。所以,这也没有用。
我查看了 Table 和 Row 文档,似乎没有任何内容可以让我访问“生成字符串字段”方法,或者设置“for field n always use quoting”标志的方法。
我正要深入研究源代码,看看是否有一些 super secret 的调整,或者是否有办法对 CSV 进行猴子修补并根据我的意愿对其进行修改,但想知道是否有人有一些特殊知识或有过以前遇到过这个。
而且,是的,我知道我可以推出自己的 CSV 输出,但我不想重新发明经过良好测试的轮子。而且,我也知道 FasterCSV;它现在是我正在使用的 Ruby 1.9.2 的一部分,因此明确使用 FasterCSV 并没有给我带来什么特别之处。另外,我没有使用 Rails 并且无意在 Rails 中重写它,所以除非您有使用 Rails 的一小部分实现它的可爱方法,否则请不要打扰。我会否决使用这些方法中的任何一种的任何建议,只是因为你懒得读到这里。
最佳答案
好吧,有一种方法可以做到这一点,但它并不像我希望 CSV 代码允许的那样干净。
我必须继承 CSV,然后覆盖 CSV::Row.<<=
方法并添加另一个方法 forced_quote_fields=
为了能够定义我想要强制引用的字段,再加上从其他方法中提取两个 lambda。至少它适用于我想要的:
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
这就是代码。调用它:
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
结果:
# >> Ruby version: 1.9.2
# >> Quoting fields: 1
# >>
# >> 1,"2",3
# >> 2,"two too",3
# >> 3,"two, too",3
关于ruby - 如何强制 Ruby 的 CSV 输出中的一个字段用双引号引起来?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4854900/