spreadsheetkit 0.0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +11 -0
- data/Rakefile +32 -0
- data/lib/spreadsheetkit.rb +14 -0
- data/lib/spreadsheetkit/base.rb +39 -0
- data/lib/spreadsheetkit/cell.rb +26 -0
- data/lib/spreadsheetkit/font.rb +60 -0
- data/lib/spreadsheetkit/format.rb +70 -0
- data/lib/spreadsheetkit/row.rb +68 -0
- data/lib/spreadsheetkit/sheet.rb +25 -0
- data/lib/spreadsheetkit/style_parser.rb +107 -0
- data/lib/spreadsheetkit/util.rb +14 -0
- data/lib/spreadsheetkit/version.rb +3 -0
- metadata +71 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
= Spreadsheetkit
|
2
|
+
Create XLSs using HTML+CSS. The objective is the same of PDKKit, but for spreadsheets.
|
3
|
+
|
4
|
+
== Dependences
|
5
|
+
* spreadsheet ~> 0.6.5.2
|
6
|
+
* css_parser ~> 1.1.5
|
7
|
+
* nokogiri ~> 1.4.4
|
8
|
+
|
9
|
+
== Inspiration
|
10
|
+
[PDFKit] https://github.com/jdpace/PDFKit
|
11
|
+
[Stevo] https://github.com/stevo/excellent
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler'
|
5
|
+
require 'bundler/setup'
|
6
|
+
rescue LoadError
|
7
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
8
|
+
end
|
9
|
+
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
require 'rake'
|
13
|
+
require 'rake/rdoctask'
|
14
|
+
|
15
|
+
require 'rake/testtask'
|
16
|
+
|
17
|
+
Rake::TestTask.new(:test) do |t|
|
18
|
+
t.libs << 'lib'
|
19
|
+
t.libs << 'test'
|
20
|
+
t.pattern = 'test/**/*_test.rb'
|
21
|
+
t.verbose = false
|
22
|
+
end
|
23
|
+
|
24
|
+
task :default => :test
|
25
|
+
|
26
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
27
|
+
rdoc.rdoc_dir = 'rdoc'
|
28
|
+
rdoc.title = 'Spreadsheetkit'
|
29
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
30
|
+
rdoc.rdoc_files.include('README.rdoc')
|
31
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
32
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "action_controller"
|
2
|
+
require 'spreadsheet'
|
3
|
+
|
4
|
+
Mime::Type.register "application/vnd.ms-excel", :xls
|
5
|
+
|
6
|
+
module Spreadsheetkit
|
7
|
+
autoload :Base, "spreadsheetkit/base"
|
8
|
+
|
9
|
+
ActionController::Renderers.add :xls do |filename, options|
|
10
|
+
xls = Spreadsheetkit::Base.new(render_to_string(options))
|
11
|
+
send_data(xls.render, :filename => "#{filename}.xls", :type => "application/vnd.ms-excel", :disposition => "attachment")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Spreadsheetkit
|
2
|
+
autoload :Cell, "spreadsheetkit/cell"
|
3
|
+
autoload :Font, "spreadsheetkit/font"
|
4
|
+
autoload :Format, "spreadsheetkit/format"
|
5
|
+
autoload :Row, "spreadsheetkit/row"
|
6
|
+
autoload :Sheet, "spreadsheetkit/sheet"
|
7
|
+
autoload :StyleParser, "spreadsheetkit/style_parser"
|
8
|
+
autoload :Util, "spreadsheetkit/util"
|
9
|
+
|
10
|
+
class Base
|
11
|
+
include StyleParser
|
12
|
+
|
13
|
+
attr_reader :html, :xls, :sheets
|
14
|
+
|
15
|
+
def initialize(html)
|
16
|
+
@html = parse_html(Nokogiri::HTML(html))
|
17
|
+
|
18
|
+
@sheets = []
|
19
|
+
@html.css("table").each do |table|
|
20
|
+
@sheets << Spreadsheetkit::Sheet.new(table)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render
|
25
|
+
sio = StringIO.new
|
26
|
+
compile_xls
|
27
|
+
xls.write(sio)
|
28
|
+
sio.string
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def compile_xls
|
34
|
+
@xls = Spreadsheet::Workbook.new
|
35
|
+
@sheets.each { |sheet| sheet.render(@xls) }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Spreadsheetkit
|
2
|
+
|
3
|
+
class Cell
|
4
|
+
attr_accessor :td, :colspan, :content, :format
|
5
|
+
|
6
|
+
def initialize(td)
|
7
|
+
@td = td
|
8
|
+
@colspan = @td[:colspan].to_i
|
9
|
+
@content = @td.content.strip
|
10
|
+
@format = Spreadsheetkit::Format.new(@td)
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge?
|
14
|
+
@colspan > 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def additional_columns
|
18
|
+
if merge?
|
19
|
+
@colspan - 1
|
20
|
+
else
|
21
|
+
1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Spreadsheetkit
|
2
|
+
|
3
|
+
class Font
|
4
|
+
attr_reader :style
|
5
|
+
|
6
|
+
def initialize(style, inline_style)
|
7
|
+
@style = style
|
8
|
+
parse inline_style
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(inline_style)
|
12
|
+
inline_style.each do |attr, value|
|
13
|
+
case attr
|
14
|
+
when 'font-size' then font_size(value)
|
15
|
+
when 'font-style' then font_style(value)
|
16
|
+
when 'font-weight' then font_weight(value)
|
17
|
+
when 'text-decoration' then font_style(value)
|
18
|
+
when 'text-shadow' then text_shadow(value)
|
19
|
+
when 'vertical-align' then vertial_align(value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def font_size(value)
|
27
|
+
size = Spreadsheetkit::Util.number_from_size(value)
|
28
|
+
@style.size = size unless size.blank?
|
29
|
+
end
|
30
|
+
|
31
|
+
def font_style(value)
|
32
|
+
@style.italic = true if value == 'italic'
|
33
|
+
end
|
34
|
+
|
35
|
+
def font_weight(value)
|
36
|
+
if value.to_i > 0
|
37
|
+
@style.weight = value
|
38
|
+
elsif
|
39
|
+
value = value.to_sym
|
40
|
+
@style.weight = value if [:normal, :bold].include?(value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def text_decoration(value)
|
45
|
+
@style.underline = :single if value == 'underline'
|
46
|
+
end
|
47
|
+
|
48
|
+
def text_shadow(value)
|
49
|
+
@style.shadow = true
|
50
|
+
end
|
51
|
+
|
52
|
+
def vertial_align(value)
|
53
|
+
case value
|
54
|
+
when 'sub' then @style.escapement = :subscript
|
55
|
+
when 'super' then @style.escapement = :superscript
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Spreadsheetkit
|
2
|
+
|
3
|
+
class Format
|
4
|
+
attr_reader :style, :inline_style
|
5
|
+
|
6
|
+
def initialize(html)
|
7
|
+
|
8
|
+
unless html.blank?
|
9
|
+
@style = Spreadsheet::Format.new
|
10
|
+
|
11
|
+
unless html[:style].blank?
|
12
|
+
create_hash_style html[:style]
|
13
|
+
@font = Spreadsheetkit::Font.new @style.font, @inline_style
|
14
|
+
parse_style
|
15
|
+
end
|
16
|
+
|
17
|
+
merge html[:colspan]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def create_hash_style(inline_style)
|
24
|
+
@inline_style = {}
|
25
|
+
inline_style.split(';').each do |attribute|
|
26
|
+
attr, value = attribute.split(':')
|
27
|
+
unless value.blank? && attr.blank?
|
28
|
+
@inline_style.store attr.strip, value.strip
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def merge(colspan)
|
34
|
+
@style.horizontal_align = :merge if colspan.to_i > 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_style
|
38
|
+
@style.font = @font.style
|
39
|
+
inline_style.each do |attr, value|
|
40
|
+
case attr
|
41
|
+
when 'border' then border(value)
|
42
|
+
when 'text-align' then text_align(value)
|
43
|
+
when 'vertical-align' then vertial_align(value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def border(value)
|
49
|
+
#p value
|
50
|
+
#@style.horizontal_align = value.to_sym
|
51
|
+
end
|
52
|
+
def text_align(value)
|
53
|
+
@style.horizontal_align = value.to_sym
|
54
|
+
end
|
55
|
+
|
56
|
+
def text_align(value)
|
57
|
+
@style.horizontal_align = value.to_sym
|
58
|
+
end
|
59
|
+
|
60
|
+
def vertial_align(value)
|
61
|
+
case value
|
62
|
+
when /baseline|bottom|text-bottom/ then @style.vertical_align = :bottom
|
63
|
+
when /top|text-top/ then @style.vertical_align = :top
|
64
|
+
when 'middle' then @style.vertical_align = :middle
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Spreadsheetkit
|
2
|
+
|
3
|
+
class Row
|
4
|
+
attr_reader :cells, :tr, :format, :merge, :row
|
5
|
+
|
6
|
+
def initialize(tr)
|
7
|
+
@tr = tr
|
8
|
+
@format = Spreadsheetkit::Format.new(@tr)
|
9
|
+
|
10
|
+
@cells = []
|
11
|
+
@tr.css("td, th").each do|td|
|
12
|
+
cell = Spreadsheetkit::Cell.new(td)
|
13
|
+
@merge = true if cell.merge?
|
14
|
+
@cells << cell
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def merge?
|
19
|
+
merge
|
20
|
+
end
|
21
|
+
|
22
|
+
def render(row)
|
23
|
+
@row = row
|
24
|
+
apply_style
|
25
|
+
|
26
|
+
@cells.each do |cell|
|
27
|
+
cell_x = cell_x(cell)
|
28
|
+
|
29
|
+
if merge?
|
30
|
+
cell_x.upto(cell_x + cell.additional_columns) do |i|
|
31
|
+
row.set_format(i, cell.format.style)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
row.set_format(cell_x, cell.format.style)
|
35
|
+
end
|
36
|
+
row[cell_x] = cell.content
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def cell_x(cell)
|
42
|
+
index = @cells.index(cell)
|
43
|
+
|
44
|
+
if merge? && index > 0
|
45
|
+
@cells[0..index-1].each_with_index do |prev_cell, i|
|
46
|
+
index += i + prev_cell.additional_columns
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
index
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_style
|
54
|
+
@row.default_format = @format.style
|
55
|
+
@format.inline_style.each do |attr, value|
|
56
|
+
case attr
|
57
|
+
when 'height' then height(value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def height(value)
|
63
|
+
size = Spreadsheetkit::Util.number_from_size(value)
|
64
|
+
@row.height = size unless size.blank?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Spreadsheetkit
|
2
|
+
|
3
|
+
class Sheet
|
4
|
+
attr_accessor :sheet, :table, :rows, :title
|
5
|
+
|
6
|
+
def initialize(table, options = {})
|
7
|
+
@table = table
|
8
|
+
|
9
|
+
@rows = []
|
10
|
+
@table.css("tr").each{ |tr| @rows << Spreadsheetkit::Row.new(tr) }
|
11
|
+
@title = @table[:title]
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(xls)
|
15
|
+
@sheet = xls.create_worksheet
|
16
|
+
@sheet.name = @title unless @title.blank?
|
17
|
+
|
18
|
+
@rows.each_with_index do |row, row_x|
|
19
|
+
row.render @sheet.row(row_x)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Extracted from https://github.com/binarylogic/mail_style/blob/master/lib/mail_style/inline_styles.rb
|
2
|
+
# Credits: Jim Neath
|
3
|
+
|
4
|
+
module Spreadsheetkit
|
5
|
+
|
6
|
+
module StyleParser
|
7
|
+
def parse_html(html)
|
8
|
+
# Parse original html
|
9
|
+
html_document = absolutize_image_sources(html)
|
10
|
+
|
11
|
+
# Write inline styles
|
12
|
+
element_styles = {}
|
13
|
+
|
14
|
+
css_parser(html_document).each_selector do |selector, declaration, specificity|
|
15
|
+
next if selector.include?(':')
|
16
|
+
html_document.css(selector).each do |element|
|
17
|
+
declaration.to_s.split(';').each do |style|
|
18
|
+
# Split style in attribute and value
|
19
|
+
attribute, value = style.split(':').map(&:strip)
|
20
|
+
|
21
|
+
# Set element style defaults
|
22
|
+
element_styles[element] ||= {}
|
23
|
+
element_styles[element][attribute] ||= { :specificity => 0, :value => '' }
|
24
|
+
|
25
|
+
# Update attribute value if specificity is higher than previous values
|
26
|
+
if element_styles[element][attribute][:specificity] <= specificity
|
27
|
+
element_styles[element][attribute] = { :specificity => specificity, :value => value }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Loop through element styles
|
34
|
+
element_styles.each_pair do |element, attributes|
|
35
|
+
# Elements current styles
|
36
|
+
current_style = element['style'].to_s.split(';').sort
|
37
|
+
|
38
|
+
# Elements new styles
|
39
|
+
new_style = attributes.map{|attribute, style| "#{attribute}: #{update_image_urls(style[:value])}"}
|
40
|
+
|
41
|
+
# Concat styles
|
42
|
+
style = (current_style + new_style).compact.uniq.map(&:strip).sort
|
43
|
+
|
44
|
+
# Set new styles
|
45
|
+
element['style'] = style.join(';')
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return HTML
|
49
|
+
html_document
|
50
|
+
end
|
51
|
+
|
52
|
+
def css_parser(html)
|
53
|
+
parser = CssParser::Parser.new
|
54
|
+
|
55
|
+
html.xpath("//head/link[@type='text/css']" ).each do |style|
|
56
|
+
parser.load_uri! "#{Rails.root}/public#{style[:href]}"
|
57
|
+
end
|
58
|
+
|
59
|
+
parser
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def absolutize_image_sources(document)
|
64
|
+
document.css('img').each do |img|
|
65
|
+
src = img['src']
|
66
|
+
img['src'] = src.gsub(src, absolutize_url(src))
|
67
|
+
end
|
68
|
+
|
69
|
+
document
|
70
|
+
end
|
71
|
+
|
72
|
+
# Absolutize URL (Absolutize? Seriously?)
|
73
|
+
def absolutize_url(url, base_path = '')
|
74
|
+
############# Refactor
|
75
|
+
# original_url = url
|
76
|
+
#unless original_url[URI::regexp(%w[http https])]
|
77
|
+
# protocol = default_url_options[:protocol]
|
78
|
+
# protocol = "http://" if protocol.blank?
|
79
|
+
# protocol+= "://" unless protocol.include?("://")
|
80
|
+
|
81
|
+
# host = default_url_options[:host]
|
82
|
+
|
83
|
+
# [host,protocol].each{|r| original_url.gsub!(r,"") }
|
84
|
+
# host = protocol+host unless host[URI::regexp(%w[http https])]
|
85
|
+
|
86
|
+
# url = URI.join host, base_path, original_url
|
87
|
+
#end
|
88
|
+
|
89
|
+
#url.to_s
|
90
|
+
|
91
|
+
url
|
92
|
+
end
|
93
|
+
|
94
|
+
# Update image urls
|
95
|
+
def update_image_urls(style)
|
96
|
+
############# Refactor
|
97
|
+
#if default_url_options[:host].present?
|
98
|
+
# Replace urls in stylesheets
|
99
|
+
# style.gsub!($1, absolutize_url($1, 'stylesheets')) if style[/url\(['"]?(.*?)['"]?\)/i]
|
100
|
+
#end
|
101
|
+
|
102
|
+
style
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spreadsheetkit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors: []
|
8
|
+
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-23 00:00:00 +00:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Create XLSs using HTML+CSS. The objective is the same of PDKKit, but for spreadsheets.
|
18
|
+
email:
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- lib/spreadsheetkit/font.rb
|
27
|
+
- lib/spreadsheetkit/format.rb
|
28
|
+
- lib/spreadsheetkit/version.rb
|
29
|
+
- lib/spreadsheetkit/cell.rb
|
30
|
+
- lib/spreadsheetkit/sheet.rb
|
31
|
+
- lib/spreadsheetkit/row.rb
|
32
|
+
- lib/spreadsheetkit/base.rb
|
33
|
+
- lib/spreadsheetkit/style_parser.rb
|
34
|
+
- lib/spreadsheetkit/util.rb
|
35
|
+
- lib/spreadsheetkit.rb
|
36
|
+
- MIT-LICENSE
|
37
|
+
- Rakefile
|
38
|
+
- README.rdoc
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/voraz/spreadsheetkit
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: -468493951563737592
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.5.0
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: HTML+CSS to XLS
|
70
|
+
test_files: []
|
71
|
+
|