spreadsheet_architect 1.4.8 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -2
- data/README.md +117 -98
- data/Rakefile +8 -1
- data/lib/generators/spreadsheet_architect/add_project_defaults_generator.rb +12 -3
- data/lib/spreadsheet_architect.rb +19 -330
- data/lib/spreadsheet_architect/action_controller_renderers.rb +9 -19
- data/lib/spreadsheet_architect/class_methods/csv.rb +22 -0
- data/lib/spreadsheet_architect/class_methods/ods.rb +61 -0
- data/lib/spreadsheet_architect/class_methods/xlsx.rb +162 -0
- data/lib/spreadsheet_architect/exceptions.rb +47 -0
- data/lib/spreadsheet_architect/{set_mime_types.rb → mime_types.rb} +0 -2
- data/lib/spreadsheet_architect/monkey_patches/axlsx_column_width.rb +23 -0
- data/lib/spreadsheet_architect/utils.rb +223 -0
- data/lib/spreadsheet_architect/utils/xlsx.rb +126 -0
- data/lib/spreadsheet_architect/version.rb +1 -1
- data/test/rails_app/Gemfile +17 -0
- data/test/rails_app/Gemfile.lock +176 -0
- data/test/rails_app/README.md +24 -0
- data/test/rails_app/Rakefile +6 -0
- data/test/rails_app/app/assets/config/manifest.js +3 -0
- data/test/rails_app/app/assets/javascripts/application.js +16 -0
- data/test/rails_app/app/assets/javascripts/cable.js +13 -0
- data/test/rails_app/app/assets/stylesheets/application.css +15 -0
- data/test/rails_app/app/channels/application_cable/channel.rb +4 -0
- data/test/rails_app/app/channels/application_cable/connection.rb +4 -0
- data/test/rails_app/app/controllers/application_controller.rb +3 -0
- data/test/rails_app/app/controllers/spreadsheets_controller.rb +74 -0
- data/test/rails_app/app/helpers/application_helper.rb +2 -0
- data/test/rails_app/app/jobs/application_job.rb +2 -0
- data/test/rails_app/app/mailers/application_mailer.rb +4 -0
- data/test/rails_app/app/models/active_model_object.rb +14 -0
- data/test/rails_app/app/models/application_record.rb +3 -0
- data/test/rails_app/app/models/bad_plain_ruby_object.rb +3 -0
- data/test/rails_app/app/models/custom_post.rb +15 -0
- data/test/rails_app/app/models/plain_ruby_object.rb +19 -0
- data/test/rails_app/app/models/post.rb +3 -0
- data/test/rails_app/app/views/layouts/application.html.erb +14 -0
- data/test/rails_app/app/views/layouts/mailer.html.erb +13 -0
- data/test/rails_app/app/views/layouts/mailer.text.erb +1 -0
- data/test/rails_app/bin/bundle +3 -0
- data/test/rails_app/bin/rails +9 -0
- data/test/rails_app/bin/rake +9 -0
- data/test/rails_app/bin/setup +34 -0
- data/test/rails_app/bin/spring +16 -0
- data/test/rails_app/bin/update +29 -0
- data/test/rails_app/config.ru +5 -0
- data/test/rails_app/config/application.rb +15 -0
- data/test/rails_app/config/boot.rb +3 -0
- data/test/rails_app/config/cable.yml +9 -0
- data/test/rails_app/config/database.yml +16 -0
- data/test/rails_app/config/environment.rb +5 -0
- data/test/rails_app/config/environments/development.rb +54 -0
- data/test/rails_app/config/environments/production.rb +86 -0
- data/test/rails_app/config/environments/test.rb +42 -0
- data/test/rails_app/config/initializers/application_controller_renderer.rb +6 -0
- data/test/rails_app/config/initializers/assets.rb +11 -0
- data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails_app/config/initializers/cookies_serializer.rb +5 -0
- data/test/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/rails_app/config/initializers/inflections.rb +16 -0
- data/test/rails_app/config/initializers/mime_types.rb +4 -0
- data/test/rails_app/config/initializers/new_framework_defaults.rb +24 -0
- data/test/rails_app/config/initializers/session_store.rb +3 -0
- data/test/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/test/rails_app/config/locales/en.yml +23 -0
- data/test/rails_app/config/routes.rb +7 -0
- data/test/rails_app/config/secrets.yml +22 -0
- data/test/{spreadsheet_architect.sqlite3.db → rails_app/db/development.sqlite3} +0 -0
- data/test/rails_app/db/migrate/20170103234524_add_posts.rb +10 -0
- data/test/rails_app/db/schema.rb +23 -0
- data/test/rails_app/db/seeds.rb +7 -0
- data/test/rails_app/db/test.sqlite3 +0 -0
- data/test/rails_app/log/development.log +1195 -0
- data/test/rails_app/log/test.log +52766 -0
- data/test/rails_app/public/404.html +67 -0
- data/test/rails_app/public/422.html +67 -0
- data/test/rails_app/public/500.html +66 -0
- data/test/rails_app/public/apple-touch-icon-precomposed.png +0 -0
- data/test/rails_app/public/apple-touch-icon.png +0 -0
- data/test/rails_app/public/favicon.ico +0 -0
- data/test/rails_app/public/robots.txt +5 -0
- data/test/rails_app/test/controllers/spreadsheets_controller_test.rb +46 -0
- data/test/rails_app/test/models/active_model_object_test.rb +54 -0
- data/test/rails_app/test/models/bad_plain_ruby_object_test.rb +30 -0
- data/test/rails_app/test/models/csv_test.rb +60 -0
- data/test/rails_app/test/models/custom_post_test.rb +47 -0
- data/test/rails_app/test/models/ods_test.rb +68 -0
- data/test/rails_app/test/models/plain_ruby_object_test.rb +54 -0
- data/test/rails_app/test/models/post_test.rb +47 -0
- data/test/rails_app/test/models/spreadsheet_architect_utils_test.rb +68 -0
- data/test/rails_app/test/models/xlsx_test.rb +99 -0
- data/test/rails_app/test/test_helper.rb +21 -0
- data/test/rails_app/tmp/active_model_object/csv.csv +21 -0
- data/test/rails_app/tmp/active_model_object/ods.ods +0 -0
- data/test/rails_app/tmp/active_model_object/xlsx.xlsx +0 -0
- data/test/rails_app/tmp/controller_tests/alt_xlsx.xlsx +0 -0
- data/test/rails_app/tmp/controller_tests/csv.csv +1 -0
- data/test/rails_app/tmp/controller_tests/ods.ods +0 -0
- data/test/rails_app/tmp/controller_tests/xlsx.xlsx +0 -0
- data/test/rails_app/tmp/custom_posts/csv.csv +1 -0
- data/test/rails_app/tmp/custom_posts/empty.xlsx +0 -0
- data/test/rails_app/tmp/custom_posts/ods.ods +0 -0
- data/test/rails_app/tmp/custom_posts/xlsx.xlsx +0 -0
- data/test/rails_app/tmp/empty_model.csv +1 -0
- data/test/rails_app/tmp/empty_model.xlsx +0 -0
- data/test/rails_app/tmp/empty_sa.csv +0 -0
- data/test/rails_app/tmp/empty_sa.xlsx +0 -0
- data/test/rails_app/tmp/extreme.xlsx +0 -0
- data/test/rails_app/tmp/model.csv +1 -0
- data/test/rails_app/tmp/model.xlsx +0 -0
- data/test/rails_app/tmp/ods/empty_model.ods +0 -0
- data/test/rails_app/tmp/ods/empty_sa.ods +0 -0
- data/test/rails_app/tmp/ods/model.ods +0 -0
- data/test/rails_app/tmp/ods/model_options.ods +0 -0
- data/test/rails_app/tmp/ods/sa.ods +0 -0
- data/test/rails_app/tmp/options.csv +1 -0
- data/test/rails_app/tmp/plain_ruby_object/csv.csv +4 -0
- data/test/rails_app/tmp/plain_ruby_object/ods.ods +0 -0
- data/test/rails_app/tmp/plain_ruby_object/xlsx.xlsx +0 -0
- data/test/rails_app/tmp/posts/csv.csv +1 -0
- data/test/rails_app/tmp/posts/empty.xlsx +0 -0
- data/test/rails_app/tmp/posts/ods.ods +0 -0
- data/test/rails_app/tmp/posts/xlsx.xlsx +0 -0
- data/test/rails_app/tmp/sa.csv +4 -0
- data/test/rails_app/tmp/sa.xlsx +0 -0
- metadata +255 -21
- data/lib/spreadsheet_architect/axlsx_column_width_patch.rb +0 -13
- data/test/database.yml +0 -3
- data/test/helper.rb +0 -52
- data/test/tc_spreadsheet_architect.rb +0 -84
@@ -1,23 +1,13 @@
|
|
1
1
|
if defined? ActionController
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
|
3
|
+
['csv','ods','xlsx'].each do |format|
|
4
|
+
ActionController::Renderers.add(format.to_sym) do |data, options|
|
5
|
+
if data.is_a?(ActiveRecord::Relation)
|
6
|
+
options[:filename] = data.klass.name.pluralize
|
7
|
+
data = data.send("to_#{format}")
|
8
|
+
end
|
9
|
+
send_data data, type: format.to_sym, disposition: :attachment, filename: "#{options[:filename] ? options[:filename].sub(".#{format}",'') : 'data'}.#{format}"
|
6
10
|
end
|
7
|
-
send_data data, type: :xlsx, disposition: :attachment, filename: "#{options[:filename] ? options[:filename].sub('.xlsx','') : 'data'}.xlsx"
|
8
|
-
end
|
9
|
-
ActionController::Renderers.add :ods do |data, options|
|
10
|
-
if data.is_a?(ActiveRecord::Relation)
|
11
|
-
options[:filename] = data.klass.name.pluralize
|
12
|
-
data = data.to_ods
|
13
|
-
end
|
14
|
-
send_data data, type: :ods, disposition: :attachment, filename: "#{options[:filename] ? options[:filename].sub('.ods','') : 'data'}.ods"
|
15
|
-
end
|
16
|
-
ActionController::Renderers.add :csv do |data, options|
|
17
|
-
if data.is_a?(ActiveRecord::Relation)
|
18
|
-
options[:filename] = data.klass.name.pluralize
|
19
|
-
data = data.to_csv
|
20
|
-
end
|
21
|
-
send_data data, type: :csv, disposition: :attachment, filename: "#{options[:filename] ? options[:filename].sub('.csv','') : 'data'}.csv"
|
22
11
|
end
|
12
|
+
|
23
13
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module SpreadsheetArchitect
|
4
|
+
module ClassMethods
|
5
|
+
def to_csv(opts={})
|
6
|
+
opts = SpreadsheetArchitect::Utils.get_cell_data(opts, self)
|
7
|
+
options = SpreadsheetArchitect::Utils.get_options(opts, self)
|
8
|
+
|
9
|
+
CSV.generate do |csv|
|
10
|
+
if options[:headers]
|
11
|
+
options[:headers].each do |header_row|
|
12
|
+
csv << header_row
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
options[:data].each do |row_data|
|
17
|
+
csv << row_data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rodf'
|
2
|
+
|
3
|
+
module SpreadsheetArchitect
|
4
|
+
module ClassMethods
|
5
|
+
def to_ods(opts={})
|
6
|
+
return to_rodf_spreadsheet(opts).bytes
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_rodf_spreadsheet(opts={}, spreadsheet=nil)
|
10
|
+
opts = SpreadsheetArchitect::Utils.get_cell_data(opts, self)
|
11
|
+
options = SpreadsheetArchitect::Utils.get_options(opts, self)
|
12
|
+
|
13
|
+
if !spreadsheet
|
14
|
+
spreadsheet = RODF::Spreadsheet.new
|
15
|
+
end
|
16
|
+
|
17
|
+
spreadsheet.office_style :header_style, family: :cell do
|
18
|
+
if options[:header_style]
|
19
|
+
SpreadsheetArchitect::Utils.convert_styles_to_ods(options[:header_style]).each do |prop, styles|
|
20
|
+
styles.each do |k,v|
|
21
|
+
property prop.to_sym, k => v
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
spreadsheet.office_style :row_style, family: :cell do
|
28
|
+
if options[:row_style]
|
29
|
+
SpreadsheetArchitect::Utils.convert_styles_to_ods(options[:row_style]).each do |prop, styles|
|
30
|
+
styles.each do |k,v|
|
31
|
+
property prop.to_sym, k => v
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
spreadsheet.table options[:sheet_name] do
|
38
|
+
if options[:headers]
|
39
|
+
options[:headers].each do |header_row|
|
40
|
+
row do
|
41
|
+
header_row.each_with_index do |header, i|
|
42
|
+
cell header, style: :header_style
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
options[:data].each_with_index do |row_data, index|
|
49
|
+
row do
|
50
|
+
row_data.each_with_index do |y,i|
|
51
|
+
cell y, style: :row_style, type: (options[:types][i] if options[:types])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return spreadsheet
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'axlsx'
|
2
|
+
require 'axlsx_styler'
|
3
|
+
|
4
|
+
require 'spreadsheet_architect/monkey_patches/axlsx_column_width'
|
5
|
+
|
6
|
+
module SpreadsheetArchitect
|
7
|
+
module ClassMethods
|
8
|
+
def to_xlsx(opts={})
|
9
|
+
return to_axlsx_package(opts).to_stream.read
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_axlsx_package(opts={}, package=nil)
|
13
|
+
opts = SpreadsheetArchitect::Utils.get_cell_data(opts, self)
|
14
|
+
options = SpreadsheetArchitect::Utils.get_options(opts, self)
|
15
|
+
|
16
|
+
header_style = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(options[:header_style])
|
17
|
+
row_style = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(options[:row_style])
|
18
|
+
|
19
|
+
if package.nil?
|
20
|
+
package = Axlsx::Package.new
|
21
|
+
end
|
22
|
+
|
23
|
+
return package if !options[:headers] && options[:data].empty?
|
24
|
+
|
25
|
+
package.workbook.add_worksheet(name: options[:sheet_name]) do |sheet|
|
26
|
+
max_row_length = options[:data].empty? ? 0 : options[:data].max_by{|x| x.length}.length
|
27
|
+
|
28
|
+
if options[:headers]
|
29
|
+
header_style_index = package.workbook.styles.add_style(header_style)
|
30
|
+
|
31
|
+
options[:headers].each do |header_row|
|
32
|
+
missing = max_row_length - header_row.count
|
33
|
+
if missing > 0
|
34
|
+
missing.times do
|
35
|
+
header_row.push(nil)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
sheet.add_row header_row, style: header_style_index
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if options[:data].empty?
|
44
|
+
break
|
45
|
+
end
|
46
|
+
|
47
|
+
row_style_index = package.workbook.styles.add_style(row_style)
|
48
|
+
|
49
|
+
options[:data].each do |row_data|
|
50
|
+
missing = max_row_length - row_data.count
|
51
|
+
if missing > 0
|
52
|
+
missing.times do
|
53
|
+
row_data.push(nil)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
types = []
|
58
|
+
row_data.each_with_index do |x,i|
|
59
|
+
if (x.respond_to?(:empty) ? x.empty? : x.nil?)
|
60
|
+
types[i] = nil
|
61
|
+
else
|
62
|
+
if options[:column_types]
|
63
|
+
types[i] = options[:column_types][i]
|
64
|
+
end
|
65
|
+
|
66
|
+
types[i] ||= SpreadsheetArchitect::Utils::XLSX.get_type(x)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
sheet.add_row row_data, style: row_style_index, types: types
|
71
|
+
end
|
72
|
+
|
73
|
+
options[:data].first.each_with_index do |x,i|
|
74
|
+
types = []
|
75
|
+
|
76
|
+
if options[:column_types]
|
77
|
+
types[i] = options[:column_types][i]
|
78
|
+
end
|
79
|
+
|
80
|
+
types[i] ||= SpreadsheetArchitect::Utils::XLSX.get_type(x)
|
81
|
+
|
82
|
+
if [:date, :time].include?(types[i])
|
83
|
+
if types[i] == :date
|
84
|
+
format_code = 'm/d/yyyy'
|
85
|
+
else
|
86
|
+
format_code = 'yyyy/m/d h:mm AM/PM'
|
87
|
+
end
|
88
|
+
|
89
|
+
sheet.col_style(i, package.workbook.styles.add_style(format_code: format_code), row_offset: (options[:headers] ? options[:headers].count : 0))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
if options[:column_widths]
|
94
|
+
sheet.column_widths(*options[:column_widths])
|
95
|
+
end
|
96
|
+
|
97
|
+
if options[:borders] || options[:column_styles] || options[:range_styles] || options[:merges]
|
98
|
+
col_names = max_row_length > 675 ? Array('A'..'ZZZ') : Array('A'..'ZZ')
|
99
|
+
num_rows = options[:data].count
|
100
|
+
end
|
101
|
+
|
102
|
+
if options[:borders]
|
103
|
+
options[:borders].each do |x|
|
104
|
+
if x[:range].is_a?(Hash)
|
105
|
+
x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows, col_names)
|
106
|
+
end
|
107
|
+
|
108
|
+
SpreadsheetArchitect::Utils::XLSX.verify_range(x[:range], num_rows, col_names)
|
109
|
+
sheet.add_border x[:range], x[:border_styles]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if options[:column_styles]
|
114
|
+
options[:column_styles].each do |x|
|
115
|
+
start_row = !x[:include_header] && options[:headers] ? options[:headers].count : 0
|
116
|
+
|
117
|
+
package.workbook.styles do |s|
|
118
|
+
style = s.add_style row_style.merge(SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(x[:styles]))
|
119
|
+
if x[:columns].is_a?(Array) || x[:columns].is_a?(Range)
|
120
|
+
x[:columns].each do |col|
|
121
|
+
if col.is_a?(String)
|
122
|
+
col = col_names.index(col)
|
123
|
+
end
|
124
|
+
|
125
|
+
sheet.col_style(col, style, row_offset: start_row)
|
126
|
+
end
|
127
|
+
elsif x[:columns].is_a?(Integer)
|
128
|
+
sheet.col_style(x[:columns], style, row_offset: start_row)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if options[:range_styles]
|
135
|
+
options[:range_styles].each do |x|
|
136
|
+
styles = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(x[:styles])
|
137
|
+
|
138
|
+
if x[:range].is_a?(Hash)
|
139
|
+
x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows, col_names)
|
140
|
+
end
|
141
|
+
|
142
|
+
SpreadsheetArchitect::Utils::XLSX.verify_range(x[:range], num_rows, col_names)
|
143
|
+
sheet.add_style x[:range], styles
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
if options[:merges]
|
148
|
+
options[:merges].each do |x|
|
149
|
+
if x[:range].is_a?(Hash)
|
150
|
+
x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows, col_names)
|
151
|
+
end
|
152
|
+
|
153
|
+
SpreadsheetArchitect::Utils::XLSX.verify_range(x[:range], num_rows, col_names)
|
154
|
+
sheet.merge_cells x[:range]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
return package
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SpreadsheetArchitect
|
2
|
+
module Exceptions
|
3
|
+
|
4
|
+
class NoDataError < StandardError
|
5
|
+
def initialize
|
6
|
+
super("Missing :data option")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class NoInstancesError < StandardError
|
11
|
+
def initialize
|
12
|
+
super("Missing :instances option")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class IncorrectTypeError < StandardError
|
17
|
+
def initialize(option=nil)
|
18
|
+
super("Incorrect data type for :#{option} option")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class SpreadsheetColumnsNotDefinedError < StandardError
|
23
|
+
def initialize(klass=nil)
|
24
|
+
super("The spreadsheet_columns option is not defined on #{klass.name}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class InvalidRangeStylesOptionError < StandardError
|
29
|
+
def initialize(type)
|
30
|
+
super("Invalid type for range_styles #{type} option. Allowed formats are Integer, Range, or :all")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class BadRangeError < StandardError
|
35
|
+
def initialize(type)
|
36
|
+
case type
|
37
|
+
when :columns, :rows
|
38
|
+
super("Bad range passed. Some of the #{type} specified were greater than the total number of #{type}")
|
39
|
+
when :format
|
40
|
+
super('Bad range passed. Format must be as follows: A1:D4')
|
41
|
+
when :type
|
42
|
+
super('Incorrect range type. Valid types are String and Hash')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
if defined? Axlsx
|
2
|
+
module Axlsx
|
3
|
+
class Col
|
4
|
+
original_initialize = instance_method(:initialize)
|
5
|
+
define_method :initialize do |*args|
|
6
|
+
@width = nil
|
7
|
+
|
8
|
+
original_initialize.bind(self).(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def width=(v)
|
12
|
+
if v.nil?
|
13
|
+
@custom_width = false
|
14
|
+
@width = nil
|
15
|
+
elsif @width.nil? || @width < v+5
|
16
|
+
@custom_width = @best_fit = v != nil
|
17
|
+
@width = v + 5
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module SpreadsheetArchitect
|
2
|
+
module Utils
|
3
|
+
def self.str_humanize(str, capitalize = true)
|
4
|
+
str = str.sub(/\A_+/, '').gsub(/[_\.]/,' ').sub(' rescue nil','')
|
5
|
+
if capitalize
|
6
|
+
str = str.gsub(/(\A|\ )\w/){|x| x.upcase}
|
7
|
+
end
|
8
|
+
return str
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.get_cell_data(options={}, klass)
|
12
|
+
self.check_options_types
|
13
|
+
|
14
|
+
if klass.name == 'SpreadsheetArchitect'
|
15
|
+
if !options[:data]
|
16
|
+
raise SpreadsheetArchitect::Exceptions::NoDataError
|
17
|
+
end
|
18
|
+
|
19
|
+
if options[:headers] && options[:headers].is_a?(Array) && !options[:headers].empty?
|
20
|
+
headers = options[:headers]
|
21
|
+
else
|
22
|
+
headers = false
|
23
|
+
end
|
24
|
+
|
25
|
+
data = options[:data]
|
26
|
+
else
|
27
|
+
has_custom_columns = options[:spreadsheet_columns] || klass.instance_methods.include?(:spreadsheet_columns)
|
28
|
+
|
29
|
+
if !options[:instances] && defined?(ActiveRecord) && klass.ancestors.include?(ActiveRecord::Base)
|
30
|
+
options[:instances] = klass.where(nil).to_a # triggers the relation call, not sure how this works but it does
|
31
|
+
end
|
32
|
+
|
33
|
+
if !options[:instances]
|
34
|
+
raise SpreadsheetArchitect::Exceptions::NoInstancesError
|
35
|
+
end
|
36
|
+
|
37
|
+
if options[:headers].nil?
|
38
|
+
headers = klass::SPREADSHEET_OPTIONS[:headers] if defined?(klass::SPREADSHEET_OPTIONS)
|
39
|
+
headers ||= SpreadsheetArchitect.default_options[:headers]
|
40
|
+
elsif options[:headers].is_a?(Array)
|
41
|
+
headers = options[:headers]
|
42
|
+
end
|
43
|
+
|
44
|
+
if headers == false || headers.is_a?(Array)
|
45
|
+
needs_headers = false
|
46
|
+
else
|
47
|
+
headers = true
|
48
|
+
needs_headers = true
|
49
|
+
end
|
50
|
+
|
51
|
+
if has_custom_columns
|
52
|
+
headers = [] if needs_headers
|
53
|
+
columns = []
|
54
|
+
array = options[:spreadsheet_columns] || (options[:instances].empty? ? [] : options[:instances].first.spreadsheet_columns)
|
55
|
+
array.each_with_index do |x,i|
|
56
|
+
if x.is_a?(Array)
|
57
|
+
headers.push(x[0].to_s) if needs_headers
|
58
|
+
columns.push x[1]
|
59
|
+
else
|
60
|
+
headers.push(str_humanize(x.to_s)) if needs_headers
|
61
|
+
columns.push x
|
62
|
+
end
|
63
|
+
end
|
64
|
+
elsif !has_custom_columns && defined?(ActiveRecord) && klass.ancestors.include?(ActiveRecord::Base)
|
65
|
+
ignored_columns = ["id","created_at","updated_at","deleted_at"]
|
66
|
+
the_column_names = (klass.column_names - ignored_columns)
|
67
|
+
headers = the_column_names.map{|x| str_humanize(x)} if needs_headers
|
68
|
+
columns = the_column_names.map{|x| x.to_sym}
|
69
|
+
else
|
70
|
+
raise SpreadsheetArchitect::Exceptions::SpreadsheetColumnsNotDefinedError, klass
|
71
|
+
end
|
72
|
+
|
73
|
+
data = []
|
74
|
+
options[:instances].each do |instance|
|
75
|
+
if has_custom_columns && !options[:spreadsheet_columns]
|
76
|
+
row_data = []
|
77
|
+
instance.spreadsheet_columns.each do |x|
|
78
|
+
if x.is_a?(Array)
|
79
|
+
row_data.push(x[1].is_a?(Symbol) ? instance.instance_eval(x[1].to_s) : x[1])
|
80
|
+
else
|
81
|
+
row_data.push(x.is_a?(Symbol) ? instance.instance_eval(x.to_s) : x)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
data.push row_data
|
85
|
+
else
|
86
|
+
data.push columns.map{|col| col.is_a?(Symbol) ? instance.instance_eval(col.to_s) : col}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if headers && !headers[0].is_a?(Array)
|
92
|
+
headers = [headers]
|
93
|
+
end
|
94
|
+
|
95
|
+
return options.merge(headers: headers, data: data, column_types: options[:column_types])
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.get_options(options={}, klass)
|
99
|
+
if options[:headers]
|
100
|
+
if defined?(klass::SPREADSHEET_OPTIONS)
|
101
|
+
header_style = SpreadsheetArchitect.default_options[:header_style].merge(klass::SPREADSHEET_OPTIONS[:header_style] || {})
|
102
|
+
else
|
103
|
+
header_style = SpreadsheetArchitect.default_options[:header_style]
|
104
|
+
end
|
105
|
+
|
106
|
+
if options[:header_style]
|
107
|
+
header_style.merge!(options[:header_style])
|
108
|
+
elsif options[:header_style] == false
|
109
|
+
header_style = false
|
110
|
+
end
|
111
|
+
else
|
112
|
+
header_style = false
|
113
|
+
end
|
114
|
+
|
115
|
+
if options[:row_style] == false
|
116
|
+
row_style = false
|
117
|
+
else
|
118
|
+
if defined?(klass::SPREADSHEET_OPTIONS)
|
119
|
+
row_style = SpreadsheetArchitect.default_options[:row_style].merge(klass::SPREADSHEET_OPTIONS[:row_style] || {})
|
120
|
+
else
|
121
|
+
row_style = SpreadsheetArchitect.default_options[:row_style]
|
122
|
+
end
|
123
|
+
|
124
|
+
if options[:row_style]
|
125
|
+
row_style.merge!(options[:row_style])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
if defined?(klass::SPREADSHEET_OPTIONS)
|
130
|
+
sheet_name = options[:sheet_name] || klass::SPREADSHEET_OPTIONS[:sheet_name] || SpreadsheetArchitect.default_options[:sheet_name]
|
131
|
+
else
|
132
|
+
sheet_name = options[:sheet_name] || SpreadsheetArchitect.default_options[:sheet_name]
|
133
|
+
end
|
134
|
+
|
135
|
+
sheet_name ||= (klass.name == 'SpreadsheetArchitect' ? 'Sheet1' : klass.name)
|
136
|
+
|
137
|
+
return options.merge(header_style: header_style, row_style: row_style, sheet_name: sheet_name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.convert_styles_to_ods(styles={})
|
141
|
+
styles = {} unless styles.is_a?(Hash)
|
142
|
+
styles = self.stringify_keys(styles)
|
143
|
+
|
144
|
+
property_styles = {}
|
145
|
+
|
146
|
+
text_styles = {}
|
147
|
+
text_styles['font-weight'] = styles.delete('bold') ? 'bold' : styles.delete('font-weight')
|
148
|
+
text_styles['font-size'] = styles.delete('font_size') || styles.delete('font-size')
|
149
|
+
text_styles['font-style'] = styles.delete('italic') ? 'italic' : styles.delete('font-style')
|
150
|
+
if styles['underline']
|
151
|
+
styles.delete('underline')
|
152
|
+
text_styles['text-underline-style'] = 'solid'
|
153
|
+
text_styles['text-underline-type'] = 'single'
|
154
|
+
end
|
155
|
+
if styles['align']
|
156
|
+
text_styles['align'] = true
|
157
|
+
end
|
158
|
+
if styles['color'].respond_to?(:sub) && !styles['color'].empty?
|
159
|
+
text_styles['color'] = "##{styles.delete('color').sub('#','')}"
|
160
|
+
end
|
161
|
+
text_styles.delete_if{|_,v| v.nil?}
|
162
|
+
property_styles['text'] = text_styles
|
163
|
+
|
164
|
+
cell_styles = {}
|
165
|
+
styles['background_color'] ||= styles.delete('background-color')
|
166
|
+
if styles['background_color'].respond_to?(:sub) && !styles['background_color'].empty?
|
167
|
+
cell_styles['background-color'] = "##{styles.delete('background_color').sub('#','')}"
|
168
|
+
end
|
169
|
+
|
170
|
+
cell_styles.delete_if{|_,v| v.nil?}
|
171
|
+
property_styles['cell'] = cell_styles
|
172
|
+
|
173
|
+
return property_styles
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def self.check_type(options, option_name, type)
|
179
|
+
unless options[option_name].nil?
|
180
|
+
valid = false
|
181
|
+
|
182
|
+
if type.is_a?(Array)
|
183
|
+
valid = type.any?{|t| options[option_name].is_a?(t)}
|
184
|
+
elsif options[option_name].is_a?(type)
|
185
|
+
valid = true
|
186
|
+
end
|
187
|
+
|
188
|
+
if valid
|
189
|
+
raise SpreadsheetArchitect::Exceptions::IncorrectTypeError option_name
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.check_options_types(options={})
|
195
|
+
self.check_type(options, :spreadsheet_columns, Array)
|
196
|
+
self.check_type(options, :instances, Array)
|
197
|
+
self.check_type(options, :headers, [TrueClass, FalseClass, Array])
|
198
|
+
self.check_type(options, :sheet_name, String)
|
199
|
+
self.check_type(options, :header_style, Hash)
|
200
|
+
self.check_type(options, :row_style, Hash)
|
201
|
+
self.check_type(options, :column_styles, Array)
|
202
|
+
self.check_type(options, :range_styles, Array)
|
203
|
+
self.check_type(options, :merges, Array)
|
204
|
+
self.check_type(options, :borders, Array)
|
205
|
+
self.check_type(options, :column_widths, Array)
|
206
|
+
end
|
207
|
+
|
208
|
+
# only converts the first 2 levels
|
209
|
+
def self.stringify_keys(hash={})
|
210
|
+
new_hash = {}
|
211
|
+
hash.each do |k,v|
|
212
|
+
if v.is_a?(Hash)
|
213
|
+
v.each do |k2,v2|
|
214
|
+
new_hash[k2.to_s] = v2
|
215
|
+
end
|
216
|
+
else
|
217
|
+
new_hash[k.to_s] = v
|
218
|
+
end
|
219
|
+
end
|
220
|
+
return new_hash
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|