spreadsheet_architect 1.4.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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