Jim MacKenzie
Jim MacKenzie

Reputation: 222

CSV.generate and converters?

I'm trying to create a converter to remove newline characters from CSV output.

I've got:

nonewline=lambda do |s|
  s.gsub(/(\r?\n)+/,' ')
end

I've verified that this works properly IF I load a variable and then run something like:

csv=CSV(variable,:converters=>[nonewline])

However, I'm attempting to use this code to update a bunch of preexisting code using CSV.generate, and it does not appear to work at all.

CSV.generate(:converters=>[nonewline]) do |csv|
  csv << ["hello\ngoodbye"]
end

returns:

"\"hello\ngoodbye\"\n"

I've tried quite a few things as well as trying other examples I've found online, and it appears as though :converters has no effect when used with CSV.generate.

Is this correct, or is there something I'm missing?

Upvotes: 1

Views: 1771

Answers (2)

Jim MacKenzie
Jim MacKenzie

Reputation: 222

Attempting to remove newlines using :converters did not work.

I had to override the << method from csv.rb adding the following code to it:

# Change all CR/NL's into one space
row.map! { |element|
  if element.is_a?(String)
    element.gsub(/(\r?\n)+/,' ')
  else
    element
  end
}

Placed right before

output = row.map(&@quote).join(@col_sep) + @row_sep # quote and separate

at line 21.

I would think this would be a good patch to CSV, as newlines will always produce bad CSV output.

Upvotes: 0

Arup Rakshit
Arup Rakshit

Reputation: 118299

You need to write your converter as as below :

CSV::Converters[:nonewline] = lambda do |s|
  s.gsub(/(\r?\n)+/,' ')
end

Then do :

CSV.generate(:converters => [:nonewline]) do |csv|
  csv << ["hello\ngoodbye"]
end

Read the documentation Converters .

Okay, above part I didn't remove, as to show you how to write the custom CSV converters. The way you wrote it is incorrect.

Read the documentation of CSV::generate

This method wraps a String you provide, or an empty default String, in a CSV object which is passed to the provided block. You can use the block to append CSV rows to the String and when the block exits, the final String will be returned.

After reading the docs, it is quite clear that this method is for writing to a csv file, not for reading. Now all the converters options ( like :converters, :header_converters) is applied, when you are reading a CSV file, but not applied when you are writing into a CSV file.

Let me show you 2 examples to illustrate this more clearly.

require 'csv'

string = <<_
foo,bar
baz,quack
_

File.write('a',string)

CSV::Converters[:upcase] = lambda do |s|
  s.upcase
end

I am reading from a CSV file, so :converters option is applied to it.

CSV.open('a','r',:converters => :upcase) do |csv|
  puts csv.read
end

output

# >> FOO
# >> BAR
# >> BAZ
# >> QUACK

Now I am writing into the CSV file, converters option is not applied.

CSV.open('a','w',:converters => :upcase) do |csv|
  csv << ['dog','cat']
end

CSV.read('a') # => [["dog", "cat"]]

Upvotes: 1

Related Questions