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.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -2
  3. data/README.md +117 -98
  4. data/Rakefile +8 -1
  5. data/lib/generators/spreadsheet_architect/add_project_defaults_generator.rb +12 -3
  6. data/lib/spreadsheet_architect.rb +19 -330
  7. data/lib/spreadsheet_architect/action_controller_renderers.rb +9 -19
  8. data/lib/spreadsheet_architect/class_methods/csv.rb +22 -0
  9. data/lib/spreadsheet_architect/class_methods/ods.rb +61 -0
  10. data/lib/spreadsheet_architect/class_methods/xlsx.rb +162 -0
  11. data/lib/spreadsheet_architect/exceptions.rb +47 -0
  12. data/lib/spreadsheet_architect/{set_mime_types.rb → mime_types.rb} +0 -2
  13. data/lib/spreadsheet_architect/monkey_patches/axlsx_column_width.rb +23 -0
  14. data/lib/spreadsheet_architect/utils.rb +223 -0
  15. data/lib/spreadsheet_architect/utils/xlsx.rb +126 -0
  16. data/lib/spreadsheet_architect/version.rb +1 -1
  17. data/test/rails_app/Gemfile +17 -0
  18. data/test/rails_app/Gemfile.lock +176 -0
  19. data/test/rails_app/README.md +24 -0
  20. data/test/rails_app/Rakefile +6 -0
  21. data/test/rails_app/app/assets/config/manifest.js +3 -0
  22. data/test/rails_app/app/assets/javascripts/application.js +16 -0
  23. data/test/rails_app/app/assets/javascripts/cable.js +13 -0
  24. data/test/rails_app/app/assets/stylesheets/application.css +15 -0
  25. data/test/rails_app/app/channels/application_cable/channel.rb +4 -0
  26. data/test/rails_app/app/channels/application_cable/connection.rb +4 -0
  27. data/test/rails_app/app/controllers/application_controller.rb +3 -0
  28. data/test/rails_app/app/controllers/spreadsheets_controller.rb +74 -0
  29. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  30. data/test/rails_app/app/jobs/application_job.rb +2 -0
  31. data/test/rails_app/app/mailers/application_mailer.rb +4 -0
  32. data/test/rails_app/app/models/active_model_object.rb +14 -0
  33. data/test/rails_app/app/models/application_record.rb +3 -0
  34. data/test/rails_app/app/models/bad_plain_ruby_object.rb +3 -0
  35. data/test/rails_app/app/models/custom_post.rb +15 -0
  36. data/test/rails_app/app/models/plain_ruby_object.rb +19 -0
  37. data/test/rails_app/app/models/post.rb +3 -0
  38. data/test/rails_app/app/views/layouts/application.html.erb +14 -0
  39. data/test/rails_app/app/views/layouts/mailer.html.erb +13 -0
  40. data/test/rails_app/app/views/layouts/mailer.text.erb +1 -0
  41. data/test/rails_app/bin/bundle +3 -0
  42. data/test/rails_app/bin/rails +9 -0
  43. data/test/rails_app/bin/rake +9 -0
  44. data/test/rails_app/bin/setup +34 -0
  45. data/test/rails_app/bin/spring +16 -0
  46. data/test/rails_app/bin/update +29 -0
  47. data/test/rails_app/config.ru +5 -0
  48. data/test/rails_app/config/application.rb +15 -0
  49. data/test/rails_app/config/boot.rb +3 -0
  50. data/test/rails_app/config/cable.yml +9 -0
  51. data/test/rails_app/config/database.yml +16 -0
  52. data/test/rails_app/config/environment.rb +5 -0
  53. data/test/rails_app/config/environments/development.rb +54 -0
  54. data/test/rails_app/config/environments/production.rb +86 -0
  55. data/test/rails_app/config/environments/test.rb +42 -0
  56. data/test/rails_app/config/initializers/application_controller_renderer.rb +6 -0
  57. data/test/rails_app/config/initializers/assets.rb +11 -0
  58. data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  59. data/test/rails_app/config/initializers/cookies_serializer.rb +5 -0
  60. data/test/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/test/rails_app/config/initializers/inflections.rb +16 -0
  62. data/test/rails_app/config/initializers/mime_types.rb +4 -0
  63. data/test/rails_app/config/initializers/new_framework_defaults.rb +24 -0
  64. data/test/rails_app/config/initializers/session_store.rb +3 -0
  65. data/test/rails_app/config/initializers/wrap_parameters.rb +14 -0
  66. data/test/rails_app/config/locales/en.yml +23 -0
  67. data/test/rails_app/config/routes.rb +7 -0
  68. data/test/rails_app/config/secrets.yml +22 -0
  69. data/test/{spreadsheet_architect.sqlite3.db → rails_app/db/development.sqlite3} +0 -0
  70. data/test/rails_app/db/migrate/20170103234524_add_posts.rb +10 -0
  71. data/test/rails_app/db/schema.rb +23 -0
  72. data/test/rails_app/db/seeds.rb +7 -0
  73. data/test/rails_app/db/test.sqlite3 +0 -0
  74. data/test/rails_app/log/development.log +1195 -0
  75. data/test/rails_app/log/test.log +52766 -0
  76. data/test/rails_app/public/404.html +67 -0
  77. data/test/rails_app/public/422.html +67 -0
  78. data/test/rails_app/public/500.html +66 -0
  79. data/test/rails_app/public/apple-touch-icon-precomposed.png +0 -0
  80. data/test/rails_app/public/apple-touch-icon.png +0 -0
  81. data/test/rails_app/public/favicon.ico +0 -0
  82. data/test/rails_app/public/robots.txt +5 -0
  83. data/test/rails_app/test/controllers/spreadsheets_controller_test.rb +46 -0
  84. data/test/rails_app/test/models/active_model_object_test.rb +54 -0
  85. data/test/rails_app/test/models/bad_plain_ruby_object_test.rb +30 -0
  86. data/test/rails_app/test/models/csv_test.rb +60 -0
  87. data/test/rails_app/test/models/custom_post_test.rb +47 -0
  88. data/test/rails_app/test/models/ods_test.rb +68 -0
  89. data/test/rails_app/test/models/plain_ruby_object_test.rb +54 -0
  90. data/test/rails_app/test/models/post_test.rb +47 -0
  91. data/test/rails_app/test/models/spreadsheet_architect_utils_test.rb +68 -0
  92. data/test/rails_app/test/models/xlsx_test.rb +99 -0
  93. data/test/rails_app/test/test_helper.rb +21 -0
  94. data/test/rails_app/tmp/active_model_object/csv.csv +21 -0
  95. data/test/rails_app/tmp/active_model_object/ods.ods +0 -0
  96. data/test/rails_app/tmp/active_model_object/xlsx.xlsx +0 -0
  97. data/test/rails_app/tmp/controller_tests/alt_xlsx.xlsx +0 -0
  98. data/test/rails_app/tmp/controller_tests/csv.csv +1 -0
  99. data/test/rails_app/tmp/controller_tests/ods.ods +0 -0
  100. data/test/rails_app/tmp/controller_tests/xlsx.xlsx +0 -0
  101. data/test/rails_app/tmp/custom_posts/csv.csv +1 -0
  102. data/test/rails_app/tmp/custom_posts/empty.xlsx +0 -0
  103. data/test/rails_app/tmp/custom_posts/ods.ods +0 -0
  104. data/test/rails_app/tmp/custom_posts/xlsx.xlsx +0 -0
  105. data/test/rails_app/tmp/empty_model.csv +1 -0
  106. data/test/rails_app/tmp/empty_model.xlsx +0 -0
  107. data/test/rails_app/tmp/empty_sa.csv +0 -0
  108. data/test/rails_app/tmp/empty_sa.xlsx +0 -0
  109. data/test/rails_app/tmp/extreme.xlsx +0 -0
  110. data/test/rails_app/tmp/model.csv +1 -0
  111. data/test/rails_app/tmp/model.xlsx +0 -0
  112. data/test/rails_app/tmp/ods/empty_model.ods +0 -0
  113. data/test/rails_app/tmp/ods/empty_sa.ods +0 -0
  114. data/test/rails_app/tmp/ods/model.ods +0 -0
  115. data/test/rails_app/tmp/ods/model_options.ods +0 -0
  116. data/test/rails_app/tmp/ods/sa.ods +0 -0
  117. data/test/rails_app/tmp/options.csv +1 -0
  118. data/test/rails_app/tmp/plain_ruby_object/csv.csv +4 -0
  119. data/test/rails_app/tmp/plain_ruby_object/ods.ods +0 -0
  120. data/test/rails_app/tmp/plain_ruby_object/xlsx.xlsx +0 -0
  121. data/test/rails_app/tmp/posts/csv.csv +1 -0
  122. data/test/rails_app/tmp/posts/empty.xlsx +0 -0
  123. data/test/rails_app/tmp/posts/ods.ods +0 -0
  124. data/test/rails_app/tmp/posts/xlsx.xlsx +0 -0
  125. data/test/rails_app/tmp/sa.csv +4 -0
  126. data/test/rails_app/tmp/sa.xlsx +0 -0
  127. metadata +255 -21
  128. data/lib/spreadsheet_architect/axlsx_column_width_patch.rb +0 -13
  129. data/test/database.yml +0 -3
  130. data/test/helper.rb +0 -52
  131. data/test/tc_spreadsheet_architect.rb +0 -84
@@ -1,23 +1,13 @@
1
1
  if defined? ActionController
2
- ActionController::Renderers.add :xlsx do |data, options|
3
- if data.is_a?(ActiveRecord::Relation)
4
- options[:filename] = data.klass.name.pluralize
5
- data = data.to_xlsx
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
@@ -5,6 +5,4 @@ if defined? Mime
5
5
  unless defined? Mime::ODS
6
6
  Mime::Type.register "application/vnd.oasis.opendocument.spreadsheet", :ods
7
7
  end
8
- else
9
- puts "Mime module not defined. Skipping registration of xlsx & ods"
10
8
  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