Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

* to_excel 1.0

  • Loading branch information...
commit 94964df4da6c19c86b13cc0b58933bfe8ea5eff2 0 parents
anoiaque authored
40 lib/to_excel.rb
... ... @@ -0,0 +1,40 @@
  1 +class Array
  2 + def to_excel(options = {})
  3 + output = '<?xml version="1.0" encoding="UTF-8"?><Workbook xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office"><Worksheet ss:Name="Sheet1"><Table>'
  4 +
  5 + if self.first.is_a?(Array) && self.first.any?
  6 + unless options[:headers] == false
  7 + output << "<Row>"
  8 +
  9 + if options[:headers].is_a? Array
  10 + options[:headers].each do |title|
  11 + output << "<Cell><Data ss:Type=\"String\">#{title}</Data></Cell>"
  12 + end
  13 + else
  14 + klasses = self.map(&:first).map(&:class)
  15 + klasses.each do |klass|
  16 + options[:map][klass].each do |attribute|
  17 + output << "<Cell><Data ss:Type=\"String\">#{klass.human_attribute_name(attribute)}</Data></Cell>"
  18 + end
  19 + end
  20 + end
  21 +
  22 + output << "</Row>"
  23 + end
  24 +
  25 + (0..self.map(&:length).max - 1).each do |n|
  26 + items = self.map {|collection| collection[n] || collection.first.class.new}
  27 + output << "<Row>"
  28 + items.each do |item|
  29 + options[:map][item.class].each do |column|
  30 + value = column.is_a?(Array) ? column.inject(item){|result,column| result.send(column) if result} : item.send(column)
  31 + output << "<Cell><Data ss:Type=\"#{value.is_a?(Integer) ? 'Number' : 'String'}\">#{value}</Data></Cell>"
  32 + end
  33 + end
  34 + output << "</Row>"
  35 + end
  36 + end
  37 +
  38 + output << '</Table></Worksheet></Workbook>'
  39 + end
  40 +end
49 test/to_excel_helper.rb
... ... @@ -0,0 +1,49 @@
  1 +begin
  2 + require 'rubygems'
  3 + require 'test/unit'
  4 + require 'active_support'
  5 + require 'to_excel'
  6 +end
  7 +
  8 +class User
  9 + COLUMNS = %w(id name age)
  10 +
  11 + attr_accessor *COLUMNS
  12 +
  13 + def self.human_attribute_name(attribute)
  14 + attribute.to_s.humanize
  15 + end
  16 +
  17 + def initialize(params={})
  18 + params.each { |key, value| self.send("#{key}=", value); }
  19 + self
  20 + end
  21 +
  22 + def attributes
  23 + COLUMNS.inject({}) { |attributes, attribute| attributes.merge(attribute => send(attribute)) }
  24 + end
  25 +
  26 + def is_old?
  27 + age > 40
  28 + end
  29 +end
  30 +
  31 +class Computer
  32 + COLUMNS = %w(id brand portable user)
  33 +
  34 + attr_accessor *COLUMNS
  35 +
  36 + def self.human_attribute_name(attribute)
  37 + attribute.to_s.humanize
  38 + end
  39 +
  40 + def initialize(params={})
  41 + params.each { |key, value| self.send("#{key}=", value); }
  42 + self
  43 + end
  44 +
  45 + def attributes
  46 + COLUMNS.inject({}) { |attributes, attribute| attributes.merge(attribute => send(attribute)) }
  47 + end
  48 +
  49 +end
104 test/to_excel_test.rb
... ... @@ -0,0 +1,104 @@
  1 +require 'to_excel_helper'
  2 +
  3 +class ToExcelTest < Test::Unit::TestCase
  4 +
  5 + def setup
  6 + @users = [
  7 + User.new(:id => 1, :name => 'Dupont', :age => 25),
  8 + User.new(:id => 2, :name => 'Martin', :age => 22)
  9 + ]
  10 + @computers =[
  11 + Computer.new(:id => 1, :brand => 'Apple', :portable => true, :user => @users.first),
  12 + Computer.new(:id => 2, :brand => 'Dell', :portable => false, :user => @users[1])
  13 + ]
  14 + end
  15 +
  16 + def test_should_be_array_of_arrays_not_empty
  17 + assert_equal build_document(nil), [].to_excel
  18 + assert_equal build_document(nil), [[]].to_excel
  19 + assert_equal build_document(nil), @users.to_excel
  20 + end
  21 +
  22 + def test_with_no_headers
  23 + document = build_document(
  24 + row(cell('Number', '25'), cell('Number', '1'), cell('String', 'Dupont')),
  25 + row(cell('Number', '22'), cell('Number', '2'), cell('String', 'Martin'))
  26 + )
  27 + assert_equal document, [@users].to_excel(:headers => false, :map => {User => [:age, :id, :name]})
  28 + end
  29 +
  30 + def test_with_methods
  31 + document = build_document(
  32 + row(cell('String', 'Age'), cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Is old?')),
  33 + row(cell('Number', '25'), cell('Number', '1'), cell('String', 'Dupont'), cell('String', 'false')),
  34 + row(cell('Number', '22'), cell('Number', '2'), cell('String', 'Martin'), cell('String', 'false'))
  35 + )
  36 + assert_equal document, [@users].to_excel(:map => { User => [:age, :id, :name, :is_old?]})
  37 + end
  38 +
  39 + def test_with_nested_attribute
  40 + document = build_document(
  41 + row(cell('String', 'Id'), cell('String', 'Username')),
  42 + row(cell('Number', '1'), cell('String', 'Dupont')),
  43 + row(cell('Number', '2'), cell('String', 'Martin'))
  44 + )
  45 + assert_equal document, [@computers].to_excel(:map => { Computer => [:id, [:user, :name]]})
  46 + end
  47 +
  48 + def test_with_two_collections
  49 + document = build_document(
  50 + row(cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Id'), cell('String', 'Brand')),
  51 + row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
  52 + row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell'))
  53 + )
  54 + assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]})
  55 + end
  56 +
  57 + def test_with_one_collection
  58 + document = build_document(
  59 + row(cell('String', 'Id'), cell('String', 'Username')),
  60 + row(cell('Number', '1'), cell('String', 'Dupont')),
  61 + row(cell('Number', '2'), cell('String', 'Martin'))
  62 + )
  63 + assert_equal document, [@computers].to_excel(:map => {Computer => [:id,[:user, :name]]})
  64 + end
  65 +
  66 + def test_should_render_empty_cells_unless_collections_of_same_size
  67 + @users << User.new(:id => 3, :name => 'Durand', :age => 23)
  68 + document = build_document(
  69 + row(cell('String', 'Id'), cell('String', 'Name'), cell('String', 'Id'), cell('String', 'Brand')),
  70 + row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
  71 + row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell')),
  72 + row(cell('Number', '3'), cell('String', 'Durand'), cell('String', ''), cell('String', ''))
  73 + )
  74 + assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]})
  75 + end
  76 +
  77 + def test_with_own_header
  78 + document = build_document(
  79 + row(cell('String', 'User Id'), cell('String', 'Nom'), cell('String', 'Computer Id'), cell('String', 'Marque')),
  80 + row(cell('Number', '1'), cell('String', 'Dupont'), cell('Number', '1'), cell('String', 'Apple')),
  81 + row(cell('Number', '2'), cell('String', 'Martin'), cell('Number', '2'), cell('String', 'Dell'))
  82 + )
  83 + header = ['User Id', 'Nom', 'Computer Id', 'Marque']
  84 + assert_equal document, [@users, @computers].to_excel(:map => { User => [:id,:name], Computer => [:id,:brand]}, :headers => header)
  85 + end
  86 + #TODO generate default map from class columns if no map option passed
  87 + def test_with_no_map_option
  88 +
  89 + end
  90 +
  91 +private
  92 +
  93 + def build_document(*rows)
  94 + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Workbook xmlns:x=\"urn:schemas-microsoft-com:office:excel\" xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:html=\"http://www.w3.org/TR/REC-html40\" xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><Worksheet ss:Name=\"Sheet1\"><Table>#{rows}</Table></Worksheet></Workbook>"
  95 + end
  96 +
  97 + def row(*cells)
  98 + "<Row>#{cells}</Row>"
  99 + end
  100 +
  101 + def cell(type, content)
  102 + "<Cell><Data ss:Type=\"#{type}\">#{content}</Data></Cell>"
  103 + end
  104 +end

0 comments on commit 94964df

Please sign in to comment.
Something went wrong with that request. Please try again.