to_spreadsheet 0.9.3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,107 @@
1
+ to_spreadsheet is a gem that lets you render xls from your existing haml/erb views from Rails (>= 3.0). !https://secure.travis-ci.org/glebm/to_spreadsheet.png?branch=master(Build Status)!:http://travis-ci.org/glebm/to_spreadsheet
2
+
3
+ h2. Installation
4
+
5
+ Add it to your Gemfile:
6
+
7
+ bc. gem 'to_spreadsheet'
8
+
9
+ h2. Usage
10
+
11
+ In your controller:
12
+
13
+ bc. # my_thingies_controller.rb
14
+ class MyThingiesController < ApplicationController
15
+ respond_to :xls, :html
16
+ def index
17
+ @my_thingies = MyItem.all
18
+ respond_to do |format|
19
+ format.html {}
20
+ format.xlsx { render xlsx: :index, filename: "thingies_index" }
21
+ end
22
+ end
23
+ end
24
+
25
+ In your view partial:
26
+
27
+ bc. # _my_items.haml
28
+ %table
29
+ %caption My items
30
+ %thead
31
+ %tr
32
+ %td ID
33
+ %td Name
34
+ %tbody
35
+ - my_items.each do |my_item|
36
+ %tr
37
+ %td.number= my_item.id
38
+ %td= my_item.name
39
+ %tfoot
40
+ %tr
41
+ %td(colspan="2") #{my_items.length}
42
+
43
+ In your index.xls.haml:
44
+
45
+ bc. # index.xls.haml
46
+ = render 'my_items', my_items: @my_items
47
+
48
+ In your index.html.haml:
49
+
50
+ bc. # index.html.haml
51
+ = link_to 'Download spreadsheet', my_items_url(format: :xlsx)
52
+ = render 'my_items', my_items: @my_items
53
+
54
+ h3. Formatting
55
+
56
+ You can define formats in your view file (local to the view) or in the initializer
57
+
58
+ bc. format_xls 'table.my-table' do
59
+ workbook use_autowidth: true
60
+ sheet orientation: landscape
61
+
62
+ format 'th', b: true # bold
63
+ format 'tbody tr', color: lambda { |row| 'ddffdd' if row.index.odd? }
64
+ format 'A3:B10', i: true # italic
65
+ format column: 0, width: 35
66
+ format 'td.custom', lambda { |cell| modify cell somehow.}
67
+
68
+ # default value (fallback value when value is blank or 0 for integer / float)
69
+ default 'td.price', 10
70
+
71
+ For the full list of supported properties head here: http://rubydoc.info/github/randym/axlsx/Axlsx/Cell
72
+ In addition, for column formats, Axlsx columnInfo properties are also supported
73
+
74
+ h3. Themes
75
+
76
+ You can define "themes" - blocks of formatting code:
77
+
78
+ bc. ToSpreadsheet.theme :zebra do
79
+ format 'tr', color: lambda { |row| 'ddffdd' if row.index.odd? }
80
+
81
+ And then use them:
82
+
83
+ bc. format_xls 'table.zebra', ToSpreadsheet.theme(:zebra)
84
+
85
+ h3. Types
86
+
87
+ The default theme uses class names on td/th to cast values.
88
+ Here is the list of class to type mapping:
89
+
90
+ |_. ==CSS== class |_. Format |
91
+ | decimal or float | Decimal |
92
+ | num or int | Integer |
93
+ | datetime | DateTime (Chronic.parse) |
94
+ | date | Date (Date.parse) |
95
+ | time | Time (Chronic.parse) |
96
+
97
+ h3. Styling
98
+
99
+
100
+ h3. Default values
101
+
102
+ Add a `data-default="default value"` attribute to a cell to use the value as a default if the model value is nil.
103
+
104
+ h3. Worksheets
105
+
106
+ Every table in the view will be converted to a separate sheet.
107
+ The sheet title will be assigned to the value of the table's <caption> element if it exists.
data/Rakefile CHANGED
@@ -1,13 +1,22 @@
1
1
  # encoding: UTF-8
2
2
  require 'rubygems'
3
- begin
4
- require 'bundler/setup'
5
- rescue LoadError
6
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
- end
3
+ require 'bundler/setup'
8
4
 
9
5
  require 'rake'
10
6
  require 'rdoc/task'
11
7
  require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task :env do
11
+ $: << File.expand_path('lib', File.dirname(__FILE__))
12
+ require 'to_spreadsheet'
13
+ include ToSpreadsheet::Helpers
14
+ end
12
15
 
13
- RSpec::Core::RakeTask.new(:spec)
16
+ task :write_test_xlsx => :env do
17
+ require 'haml'
18
+ path = '/tmp/spreadsheet.xlsx'
19
+ html = Haml::Engine.new(File.read('spec/support/table.html.haml')).render
20
+ ToSpreadsheet::Axlsx::Renderer.to_package(html).serialize(path)
21
+ puts "Written to #{path}"
22
+ end
@@ -2,21 +2,27 @@ require 'active_support'
2
2
  require 'action_controller/metal/renderers'
3
3
  require 'action_controller/metal/responder'
4
4
 
5
- require 'to_spreadsheet/xls'
5
+ require 'to_spreadsheet/axlsx/renderer'
6
6
 
7
- # This will let us do thing like `render :xls => 'index'`
8
- # This is also how Rails internally implements its :json and :xml renderers
9
- # Rarely used, nevertheless public API
10
- ActionController::Renderers.add :xls do |template, options|
11
- send_data ToSpreadsheet::XLS.to_io(render_to_string(template, options)).read, :type => :xls
7
+ # This will let us do thing like `render :xlsx => 'index'`
8
+ # This is similar to how Rails internally implements its :json and :xml renderers
9
+ ActionController::Renderers.add :xlsx do |template, options|
10
+ filename = options[:filename] || options[:template] || 'data'
11
+
12
+ html = with_context ToSpreadsheet.context.derive do
13
+ render_to_string(options[:template], options)
14
+ end
15
+
16
+ data = ToSpreadsheet::Axlsx::Renderer.to_data(html)
17
+ send_data data, type: :xlsx, disposition: %(attachment; filename="#{filename}.xlsx")
12
18
  end
13
19
 
14
20
  class ActionController::Responder
15
21
  # This sets up a default render call for when you do
16
22
  # respond_to do |format|
17
- # format.xls
23
+ # format.xlsx
18
24
  # end
19
- def to_xls
20
- controller.render :xls => controller.action_name
25
+ def to_xlsx
26
+ controller.render xlsx: controller.action_name
21
27
  end
22
- end
28
+ end
@@ -0,0 +1,86 @@
1
+ module ToSpreadsheet
2
+ module Axlsx
3
+ module Formatter
4
+ COL_INFO_PROPS = %w(bestFit collapsed customWidth hidden phonetic width).map(&:to_sym)
5
+
6
+ def apply_formats(package, context)
7
+ package.workbook.worksheets.each do |sheet|
8
+ fmt = context.formats(sheet)
9
+ fmt.workbook_props.each do |prop, value|
10
+ package.send(:"#{prop}=", value)
11
+ end
12
+ fmt.sheet_props.each do |set, props|
13
+ apply_props sheet.send(set), props, context
14
+ end
15
+ fmt.column_props.each do |v|
16
+ idx, props = v[0], v[1]
17
+ apply_props sheet.column_info[0], props.slice(*COL_INFO_PROPS), context
18
+ props = props.except(*COL_INFO_PROPS)
19
+ sheet.col_style idx, props if props.present?
20
+ end
21
+ fmt.row_props.each do |v|
22
+ idx, props = v[0], v[1]
23
+ sheet.row_style idx, props
24
+ end
25
+ fmt.range_props.each do |v|
26
+ range, props = v[0], v[1]
27
+ apply_props sheet[range], props, context
28
+ end
29
+ fmt.css_props.each do |v|
30
+ css_sel, props = v[0], v[1]
31
+ context.node_from_entity(sheet).css(css_sel).each do |node|
32
+ apply_props context.entity_from_node(node), props, context
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+ def apply_props(obj, props, context)
40
+ if props.is_a?(Proc)
41
+ context.instance_exec(obj, &props)
42
+ return
43
+ end
44
+
45
+ props = props.dup
46
+ props.each do |k, v|
47
+ props[k] = context.instance_exec(obj, &v) if v.is_a?(Proc)
48
+ end
49
+
50
+ props.each do |k, v|
51
+ next if v.nil?
52
+ if k == :default_value
53
+ unless obj.value.present? && !([:integer, :float].include?(obj.type) && obj.value.zero?)
54
+ obj.type = cell_type_from_value(v)
55
+ obj.value = v
56
+ end
57
+ else
58
+ setter = :"#{k}="
59
+ obj.send setter, v if obj.respond_to?(setter)
60
+ if obj.respond_to?(:cells)
61
+ obj.cells.each do |cell|
62
+ cell.send setter, v if cell.respond_to?(setter)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def cell_type_from_value(v)
70
+ if v.is_a?(Date)
71
+ :date
72
+ elsif v.is_a?(Time)
73
+ :time
74
+ elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
75
+ :boolean
76
+ elsif v.to_s.match(/\A[+-]?\d+?\Z/) #numeric
77
+ :integer
78
+ elsif v.to_s.match(/\A[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\Z/) #float
79
+ :float
80
+ else
81
+ :string
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,52 @@
1
+ require 'axlsx'
2
+ require 'to_spreadsheet/axlsx/formatter'
3
+ module ToSpreadsheet
4
+ module Axlsx
5
+ module Renderer
6
+ include Formatter
7
+ extend self
8
+
9
+ def to_stream(html, context = ToSpreadsheet.context)
10
+ to_package(html, context).to_stream
11
+ end
12
+
13
+ def to_data(html, context = ToSpreadsheet.context)
14
+ to_package(html, context).to_stream.read
15
+ end
16
+
17
+ def to_package(html, context = ToSpreadsheet.context)
18
+ package = build_package(html, context)
19
+ apply_formats(package, context)
20
+ # Don't leak memory: clear all dom <-> axslsx associations
21
+ context.clear_assoc!
22
+ # Numbers compat
23
+ package.use_shared_strings = true
24
+ package
25
+ end
26
+
27
+ private
28
+
29
+ def build_package(html, context)
30
+ package = ::Axlsx::Package.new
31
+ spreadsheet = package.workbook
32
+ doc = Nokogiri::HTML::Document.parse(html)
33
+ context.assoc! spreadsheet, doc
34
+ doc.css('table').each_with_index do |xml_table, i|
35
+ sheet = spreadsheet.add_worksheet(
36
+ name: xml_table.css('caption').inner_text.presence || xml_table['name'] || "Sheet #{i + 1}"
37
+ )
38
+ context.assoc! sheet, xml_table
39
+ xml_table.css('tr').each do |row_node|
40
+ xls_row = sheet.add_row
41
+ context.assoc! xls_row, row_node
42
+ row_node.css('th,td').each do |cell_node|
43
+ xls_col = xls_row.add_cell cell_node.inner_text
44
+ context.assoc! xls_col, cell_node
45
+ end
46
+ end
47
+ end
48
+ package
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ module ToSpreadsheet
2
+ class Context
3
+ # Associating Axlsx entities and Nokogiri nodes
4
+ module Pairing
5
+ def assoc!(entity, node)
6
+ @entity_to_node ||= {}
7
+ @node_to_entity ||= {}
8
+ @entity_to_node[entity] = node
9
+ @node_to_entity[node] = entity
10
+ end
11
+
12
+ def entity_from_node(node)
13
+ @node_to_entity[node]
14
+ end
15
+
16
+ def node_from_entity(entity)
17
+ @entity_to_node[entity]
18
+ end
19
+
20
+ def clear_assoc!
21
+ @entity_to_node = {}
22
+ @node_to_entity = {}
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,130 @@
1
+ require 'to_spreadsheet/context/pairing'
2
+ require 'to_spreadsheet/formats'
3
+
4
+ module ToSpreadsheet
5
+ class Context
6
+ include Pairing
7
+
8
+ def initialize(wb_options = nil)
9
+ @formats = []
10
+ @current_format = Formats.new
11
+ workbook wb_options if wb_options
12
+ end
13
+
14
+ # Returns a new formats jar for a given sheet
15
+ def formats(sheet)
16
+ format = Formats.new
17
+ @formats.each do |v|
18
+ sel, fmt = v[0], v[1]
19
+ format.merge!(fmt) if selects?(sel, sheet)
20
+ end
21
+ format
22
+ end
23
+
24
+ # Check if selector matches a given sheet / cell / row
25
+ def selects?(selector, entity)
26
+ return true if !selector
27
+ type, val = selector[0], selector[1]
28
+ sheet = entity.is_a?(::Axlsx::Workbook) ? entity : (entity.respond_to?(:workbook) ? entity.workbook : entity.worksheet.workbook)
29
+ doc = node_from_entity(sheet)
30
+ case type
31
+ when :css
32
+ doc.css(val).include?(node_from_entity(entity))
33
+ when :column
34
+ return false if entity.is_a?(Axlsx::Row)
35
+ entity.index == val if entity.is_a?(Axlsx::Cell)
36
+ when :row
37
+ return entity.index == val if entity.is_a?(Axlsx::Row)
38
+ entity.row.index == val if entity.is_a?(Axlsx::Cell)
39
+ when :range
40
+ if entity.is_a?(Axlsx::Cell)
41
+ pos = entity.pos
42
+ top_left, bot_right = val.split(':').map { |s| Axlsx.name_to_indices(s) }
43
+ pos[0] >= top_left[0] && pos[0] <= bot_right[0] && pos[1] >= top_left[1] && pos[1] <= bot_right[1]
44
+ end
45
+ end
46
+ end
47
+
48
+ # current format, used internally
49
+ attr_accessor :current_format
50
+
51
+ # format_xls 'table.zebra' do
52
+ # format 'td', lambda { |cell| {b: true} if cell.row.even? }
53
+ # end
54
+ def format_xls(selector = nil, theme = nil, &block)
55
+ selector, theme = nil, selector if selector.is_a?(Proc) && !theme
56
+ add_format(selector, &theme) if theme
57
+ add_format(selector, &block) if block
58
+ self
59
+ end
60
+
61
+ # format 'td.b', b: true # bold
62
+ # format column: 0, width: 50
63
+ # format 'A1:C30', b: true
64
+ # Accepted properties: http://rubydoc.info/github/randym/axlsx/Axlsx/Cell
65
+ # column format also accepts Axlsx columnInfo settings
66
+ def format(selector = nil, options)
67
+ options = options.dup
68
+ selector = extract_selector!(selector, options)
69
+ add selector[0], selector, options
70
+ end
71
+
72
+ # sheet 'table.landscape', page_setup: { orientation: landscape }
73
+ def sheet(selector = nil, options)
74
+ options = options.dup
75
+ selector = extract_selector!(selector, options)
76
+ add :sheet, selector, options
77
+ end
78
+
79
+ # default 'td.c', 5
80
+ def default(selector, value)
81
+ options = {default_value: value}
82
+ selector = extract_selector!(selector, options)
83
+ add selector[0], selector, options
84
+ end
85
+
86
+ def add(setting, selector, value)
87
+ @current_format[setting] << [selector.try(:[], 1), value] if selector || value
88
+ end
89
+
90
+ def workbook(selector = nil, value)
91
+ add :package, selector, value
92
+ end
93
+
94
+ def apply(theme = nil, &block)
95
+ add_format &theme if theme
96
+ add_format &block if block
97
+ self
98
+ end
99
+
100
+ def derive
101
+ derived = dup
102
+ derived.current_format = derived.current_format.derive
103
+ derived
104
+ end
105
+
106
+ private
107
+
108
+ def add_format(sheet_sel = nil, &block)
109
+ format_was = @current_format
110
+ @current_format = @current_format.derive
111
+ instance_eval &block
112
+ @formats << [extract_selector!(sheet_sel), @current_format]
113
+ @current_format = format_was
114
+ end
115
+
116
+ def extract_selector!(selector, options = {})
117
+ if selector
118
+ if selector =~ /:/ && selector[0].upcase == selector[0]
119
+ return [:range, selector]
120
+ else
121
+ return [:css, selector]
122
+ end
123
+ end
124
+ [:column, :row, :range].each do |key|
125
+ return [key, options.delete(key)] if options.key?(key)
126
+ end
127
+ selector
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,56 @@
1
+ module ToSpreadsheet
2
+ class Formats
3
+ attr_accessor :styles_by_type
4
+ attr_writer :sheet_props
5
+
6
+ def [](type)
7
+ (@styles_by_type ||= {})[type.to_sym] ||= []
8
+ end
9
+
10
+ def each
11
+ @styles_by_type.each do |k, v|
12
+ yield(k, v)
13
+ end if @styles_by_type
14
+ end
15
+
16
+ # Sheet props without selectors
17
+ def sheet_props
18
+ self[:sheet].map(&:last).inject({}, &:merge)
19
+ end
20
+
21
+ # Workbook props without selectors
22
+ def workbook_props
23
+ self[:workbook].map(&:last).inject({}, &:merge)
24
+ end
25
+
26
+ def range_props
27
+ self[:range]
28
+ end
29
+
30
+ def column_props
31
+ self[:column]
32
+ end
33
+
34
+ def row_props
35
+ self[:row]
36
+ end
37
+
38
+ def css_props
39
+ self[:css]
40
+ end
41
+
42
+ def derive
43
+ derived = Formats.new
44
+ each { |type, styles| derived[type].concat(styles) }
45
+ derived
46
+ end
47
+
48
+ def merge!(other_fmt)
49
+ other_fmt.each { |type, styles| self[type].concat(styles) }
50
+ end
51
+
52
+ def inspect
53
+ "Formats(sheet: #@sheet_props, styles: #@styles_by_type)"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ module ToSpreadsheet
2
+ module Helpers
3
+
4
+ def to_spreadsheet(selector, &block)
5
+ context.apply(block)
6
+ end
7
+
8
+ def format_xls(selector = nil, &block)
9
+ context.format_xls selector, &block
10
+ end
11
+
12
+ def context
13
+ @context || ToSpreadsheet.context
14
+ end
15
+
16
+ def with_context(context, &block)
17
+ context_was = self.context
18
+ @context = context
19
+ result = block.call
20
+ @context = context_was
21
+ result
22
+ end
23
+ end
24
+ end
@@ -1,2 +1,2 @@
1
1
  require 'action_dispatch/http/mime_type'
2
- Mime::Type.register "application/vnd.ms-excel", :xls
2
+ Mime::Type.register "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :xlsx
@@ -0,0 +1,37 @@
1
+ module ToSpreadsheet::Themes
2
+ module Default
3
+ ::ToSpreadsheet.theme :default do
4
+ workbook use_autowidth: true
5
+ sheet page_setup: {
6
+ fit_to_height: 1,
7
+ fit_to_width: 1,
8
+ orientation: :landscape
9
+ }
10
+ # Set value type based on CSS class
11
+ format 'td,th', lambda { |cell|
12
+ val = cell.value
13
+ case node_from_entity(cell)[:class]
14
+ when /decimal|float/
15
+ cell.type = :float
16
+ when /num|int/
17
+ cell.type = :integer
18
+ when /bool/
19
+ cell.type = :boolean
20
+ # Parse (date)times and dates with Chronic and Date.parse
21
+ when /datetime|time/
22
+ val = Chronic.parse(val)
23
+ if val
24
+ cell.type = :time
25
+ cell.value = val
26
+ end
27
+ when /date/
28
+ val = (Date.parse(val) rescue val)
29
+ if val.present?
30
+ cell.type = :date
31
+ cell.value = val
32
+ end
33
+ end
34
+ }
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module ToSpreadsheet
2
- VERSION = '0.9.3'
3
- end
2
+ VERSION = '1.0.0.rc1'
3
+ end
File without changes
@@ -2,7 +2,23 @@ require 'nokogiri'
2
2
  require 'to_spreadsheet/action_pack_renderers'
3
3
  require 'to_spreadsheet/mime_types'
4
4
  require 'to_spreadsheet/version'
5
+ require 'to_spreadsheet/helpers'
6
+ require 'to_spreadsheet/context'
5
7
 
6
8
  module ToSpreadsheet
9
+ class << self
10
+ attr_accessor :context
7
11
 
8
- end
12
+ def theme(name, &formats)
13
+ @themes ||= {}
14
+ if formats
15
+ @themes[name] = formats
16
+ else
17
+ @themes[name]
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'to_spreadsheet/themes/default'
24
+ ToSpreadsheet.context = ToSpreadsheet::Context.new.apply ToSpreadsheet.theme(:default)
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+
4
+
5
+ describe ToSpreadsheet::Axlsx::Formatter do
6
+ let :spreadsheet do
7
+ build_spreadsheet(haml: <<HAML)
8
+ - format_xls 'table' do
9
+ - default 'td.price', 100
10
+ %table
11
+ %tr
12
+ %td.price
13
+ %td.price 50
14
+ HAML
15
+ end
16
+
17
+ let :row do
18
+ spreadsheet.workbook.worksheets[0].rows[0]
19
+ end
20
+
21
+ context 'default values' do
22
+ it 'get set when the cell is empty' do
23
+ row.cells[0].value.should == 100
24
+ end
25
+ it 'does not get set when the cell is not empty' do
26
+ row.cells[1].value.should == 50
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe ToSpreadsheet::Axlsx::Formatter do
4
+ let :spreadsheet do
5
+ build_spreadsheet haml: <<-HAML
6
+ :ruby
7
+ format_xls do
8
+ format column: 0, width: 25
9
+ format 'tr', color: lambda { |row| 'cccccc' if row.index.odd? }
10
+ end
11
+ %table
12
+ %tr
13
+ %th
14
+ %tr
15
+ %td
16
+ HAML
17
+ end
18
+
19
+ let(:sheet) { spreadsheet.workbook.worksheets[0] }
20
+
21
+ context 'local styles' do
22
+ it 'sets column width' do
23
+ sheet.column_info[0].width.should == 25
24
+ end
25
+ it 'runs lambdas' do
26
+ sheet.rows[1].cells[0].color.rgb.should == Axlsx::Color.new(rgb: 'cccccc').rgb
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe ToSpreadsheet::Themes::Default do
4
+ let :spreadsheet do
5
+ build_spreadsheet haml: <<-HAML
6
+
7
+ %table
8
+ %tr
9
+ %td.num 20
10
+ %td.float 1
11
+ %td.date 27/05/1991
12
+ %td.date
13
+ HAML
14
+ end
15
+
16
+ let :row do
17
+ spreadsheet.workbook.worksheets[0].rows[0]
18
+ end
19
+
20
+ context 'data types' do
21
+ it 'num' do
22
+ row.cells[0].value.should == 20
23
+ end
24
+
25
+ it 'float' do
26
+ row.cells[1].type.should be :float
27
+ end
28
+
29
+ it 'date' do
30
+ row.cells[2].type.should be :date
31
+ end
32
+
33
+ it 'empty date' do
34
+ row.cells[3].type.should_not be :date
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe ToSpreadsheet::Axlsx::Renderer do
4
+ let :spreadsheet do
5
+ build_spreadsheet haml: <<-HAML
6
+ %table
7
+ %table
8
+ HAML
9
+ end
10
+
11
+ context 'worksheets' do
12
+ it 'are created 1 per <table>' do
13
+ spreadsheet.workbook.should have(2).worksheets
14
+ end
15
+ end
16
+ end
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: to_spreadsheet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
5
- prerelease:
4
+ version: 1.0.0.rc1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Gleb Mazovetskiy
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-13 00:00:00.000000000Z
12
+ date: 2012-11-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &16683240 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *16683240
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: nokogiri
27
- requirement: &16682820 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *16682820
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: spreadsheet
38
- requirement: &16682400 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *16682400
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: mocha
49
- requirement: &16681980 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,10 +69,15 @@ dependencies:
54
69
  version: '0'
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *16681980
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: sqlite3-ruby
60
- requirement: &16681560 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ! '>='
@@ -65,22 +85,38 @@ dependencies:
65
85
  version: '0'
66
86
  type: :development
67
87
  prerelease: false
68
- version_requirements: *16681560
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
69
94
  description: Rendering spreadsheets from Rails made easy
70
95
  email: glex.spb@gmail.com
71
96
  executables: []
72
97
  extensions: []
73
98
  extra_rdoc_files:
74
- - README.rdoc
99
+ - README.textile
75
100
  files:
76
- - README.rdoc
101
+ - README.textile
77
102
  - LICENSE
78
103
  - Rakefile
104
+ - lib/to_spreadsheet/action_pack_renderers.rb
105
+ - lib/to_spreadsheet/axlsx/formatter.rb
106
+ - lib/to_spreadsheet/axlsx/renderer.rb
107
+ - lib/to_spreadsheet/context/pairing.rb
108
+ - lib/to_spreadsheet/context.rb
109
+ - lib/to_spreadsheet/formats.rb
110
+ - lib/to_spreadsheet/helpers.rb
79
111
  - lib/to_spreadsheet/mime_types.rb
112
+ - lib/to_spreadsheet/themes/default.rb
80
113
  - lib/to_spreadsheet/version.rb
81
- - lib/to_spreadsheet/xls.rb
82
- - lib/to_spreadsheet/action_pack_renderers.rb
114
+ - lib/to_spreadsheet/xlsx.rb
83
115
  - lib/to_spreadsheet.rb
116
+ - spec/defaults_spec.rb
117
+ - spec/format_spec.rb
118
+ - spec/types_spec.rb
119
+ - spec/worksheets_spec.rb
84
120
  homepage: https://github.com/glebm/to_spreadsheet
85
121
  licenses: []
86
122
  post_install_message:
@@ -96,13 +132,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
132
  required_rubygems_version: !ruby/object:Gem::Requirement
97
133
  none: false
98
134
  requirements:
99
- - - ! '>='
135
+ - - ! '>'
100
136
  - !ruby/object:Gem::Version
101
- version: '0'
137
+ version: 1.3.1
102
138
  requirements: []
103
139
  rubyforge_project:
104
- rubygems_version: 1.8.17
140
+ rubygems_version: 1.8.24
105
141
  signing_key:
106
142
  specification_version: 3
107
143
  summary: Adds various html -> spreadsheet (xls, odt, etc) renderers to Rails.
108
- test_files: []
144
+ test_files:
145
+ - spec/defaults_spec.rb
146
+ - spec/format_spec.rb
147
+ - spec/types_spec.rb
148
+ - spec/worksheets_spec.rb
149
+ has_rdoc: true
data/README.rdoc DELETED
@@ -1,63 +0,0 @@
1
- to_spreadsheet is a gem that lets you render xls from your existing haml/erb views from Rails (>= 3.0).
2
-
3
- = Installation
4
-
5
- Add it to your Gemfile:
6
-
7
- gem 'to_spreadsheet'
8
-
9
- = Usage
10
-
11
- In your controller:
12
-
13
- class MyThingiesController < ApplicationController
14
- respond_to :xls, :html
15
-
16
- def index
17
- @my_thingies = MyThingie.all
18
- respond_with(@my_thingies)
19
- end
20
- end
21
-
22
- In your view partial:
23
-
24
- # _my_thingie.haml
25
- %table
26
- %caption My thingies
27
- %thead
28
- %tr
29
- %td ID
30
- %td Name
31
- %tbody
32
- - my_thingies.each do |thingie|
33
- %tr
34
- %td.number= thingie.id
35
- %td= thingie.name
36
- %tfoot
37
- %tr
38
- %td(colspan="2") #{my_thingies.length}
39
-
40
- In your index.xls.haml:
41
-
42
- = render 'my_thingies', :my_thingies => @my_thingies
43
-
44
- In your index.html.haml:
45
-
46
- = link_to 'Download XLS', my_thingies_url(:format => :xls)
47
- = render 'my_thingies', :my_thingies => @my_thingies
48
-
49
- == Formatting
50
-
51
- You can use class names on td/th for typed values. Here is the list of class to type mapping:
52
-
53
- |_ Class |_ Format |
54
- | /decimal|float/ | Decimal |
55
- | /num|int/ | Integer |
56
- | /datetime/ | DateTime |
57
- | /date/ | Date |
58
- | /time/ | Time |
59
-
60
- == Worksheets
61
-
62
- Every table in the view will be converted to a separate sheet.
63
- The sheet title will be assigned to the value of the table's <caption> element if it exists.
@@ -1,42 +0,0 @@
1
- module ToSpreadsheet
2
- require 'spreadsheet'
3
- module XLS
4
- extend self
5
-
6
- def to_io(html)
7
- spreadsheet = Spreadsheet::Workbook.new
8
- Nokogiri::HTML::Document.parse(html).css('table').each_with_index do |xml_table, i|
9
- sheet = spreadsheet.create_worksheet(:name => xml_table.css('caption').inner_text.presence || "Sheet #{i + 1}")
10
- xml_table.css('tr').each_with_index do |row_node, row|
11
- row_node.css('th,td').each_with_index do |col_node, col|
12
- sheet[row, col] = typed_node_val(col_node)
13
- end
14
- end
15
- end
16
- io = StringIO.new
17
- spreadsheet.write(io)
18
- io.rewind
19
- io
20
- end
21
-
22
- private
23
-
24
- def typed_node_val(node)
25
- val = node.inner_text
26
- case node[:class]
27
- when /decimal|float/
28
- val.to_f
29
- when /num|int/
30
- val.to_i
31
- when /datetime/
32
- DateTime.parse(val)
33
- when /date/
34
- Date.parse(val)
35
- when /time/
36
- Time.parse(val)
37
- else
38
- val
39
- end
40
- end
41
- end
42
- end