Created
May 9, 2012 19:35
-
-
Save raggi/2648240 to your computer and use it in GitHub Desktop.
csv generation, conversion, with utf16le - example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'iconv' unless "".respond_to?(:encode) | |
require 'active_support/basic_object' | |
# = EncodeIo | |
# | |
# This is essentially a basic delegation class. The reason it isn't | |
# using the delegate library from stdlib is that FasterCSV has special | |
# casing for io classes. In order to keep using this, we need #kind_of? | |
# and #class to pretend that this is the original class. | |
# | |
# This class will wrap any IO like object and pass any writes through | |
# Iconv before passing them on to the underlying IO. | |
# | |
# XXX - #putc may be bugged for big endian encodings. | |
class WF::EncodeIo < ActiveSupport::BasicObject | |
def initialize(io, from = 'UTF-8', to = 'UTF-16le//IGNORE', encode_opts = {}) | |
@io = io | |
@from, @to = from, to | |
@encode_opts = encode_opts | |
if "".respond_to?(:encode) | |
@from = Encoding.find(from) unless from.kind_of?(Encoding) | |
unless to.kind_of?(Encoding) | |
coding, ignore = to.split('//') | |
@encode_opts[:fallback] = lambda { |c| c } if ignore =~ /ignore/i | |
@to = Encoding.find(coding) | |
end | |
end | |
end | |
conversion_methods = %w[ write syswrite write_nonblock << putc print ] | |
conversion_methods.each do |m| | |
class_eval <<-RUBY, __FILE__, __LINE__ | |
def #{m}(*args) | |
@io.send __method__, *_convert(*args) | |
end | |
RUBY | |
end | |
def puts(*args) | |
# The documented IO#puts conventions claim to do this, however, in many | |
# cases it's actually "optimized", that is, reimplemented inline in C. | |
args.each do |arg| | |
line = arg.end_with?($/) ? arg : arg + $/ | |
print line | |
end | |
nil | |
end | |
def method_missing(name, *args, &block) | |
@io.send(name, *args, &block) | |
end | |
private | |
if "".respond_to?(:encode) | |
def _convert(*args) | |
args.map { |a| a.encode(@to, @from, @encode_opts) } | |
end | |
else | |
def _convert(*args) | |
args.map { |a| Iconv.conv(@to, @from, a) } | |
end | |
end | |
end | |
class WF::CSV | |
UTF16LE_BOM = "\xFF\xFE" | |
attr_reader :name, :path | |
def initialize name = 'export.csv.zip' | |
@name = name | |
Tempfile.open(@name) do |io| | |
begin | |
@path = io.path | |
ensure | |
@path = nil | |
end | |
end | |
end | |
def write | |
unless @path | |
raise ArgumentError, | |
"you must write to the CSV from within the block passed to initialize" | |
end | |
Zip::ZipOutputStream.open(@path) do |zip| | |
zip.put_next_entry(@name) | |
zip.print(UTF16LE_BOM) | |
csv = FasterCSV.new(zip, :col_sep => "\t", :row_sep => "\n") | |
yield csv | |
end | |
end | |
end | |
# Example usage: | |
WF::CSV.new("#{CGI.escape page_name}.csv.zip") do |zip| | |
zip.data do |csv| | |
csv << %w[header1 header2 header3] | |
Model.scope.find_each do |model| | |
csv << model.attributes.values_at(*%w[attr1 attr2 attr3]) | |
end | |
end | |
Mailer.send_csv_email user_email, :attachment => zip.path | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment