diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d4809..82ff620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ CHANGELOG - **Unreleased** - Switch to the `caxlsx` gem (Community Axlsx) from the legacy unmaintained `axlsx` gem. Axlsx has had a long history of being poorly maintained so this community gem improves the situation. - Require Ruby 2.3+ - - Add options `:freeze` and `:freeze_headers` for XLSX spreadsheets + - Ensure all options using Hash are automatically converted to symbol only hashes + - Add XLSX option `:freeze` to freeze custom sections of your spreadsheet + - Add XLSX option `:freeze_headers` to freeze the headers of your spreadsheet - **3.3.1** - [Issue #30](https://github.com/westonganger/spreadsheet_architect/issues/30) - Fix duplicate constant warning for XLSX_COLUMN_TYPES - **3.3.0** diff --git a/lib/spreadsheet_architect/class_methods/xlsx.rb b/lib/spreadsheet_architect/class_methods/xlsx.rb index 75116c6..de1409c 100644 --- a/lib/spreadsheet_architect/class_methods/xlsx.rb +++ b/lib/spreadsheet_architect/class_methods/xlsx.rb @@ -107,6 +107,8 @@ def to_axlsx_package(opts={}, package=nil) sheet.add_row row_data, style: styles, types: types if options[:conditional_row_styles] + options[:conditional_row_styles] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:conditional_row_styles]) + conditional_styles_for_row = SpreadsheetArchitect::Utils::XLSX.conditional_styles_for_row(options[:conditional_row_styles], row_index, row_data) unless conditional_styles_for_row.empty? @@ -127,6 +129,8 @@ def to_axlsx_package(opts={}, package=nil) end if options[:borders] + options[:borders] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:borders]) + options[:borders].each do |x| if x[:range].is_a?(Hash) x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows) @@ -139,6 +143,8 @@ def to_axlsx_package(opts={}, package=nil) end if options[:column_styles] + options[:column_styles] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:column_styles]) + options[:column_styles].each do |x| start_row = (options[:headers] ? options[:headers].count : 0) + 1 @@ -170,6 +176,8 @@ def to_axlsx_package(opts={}, package=nil) end if options[:range_styles] + options[:range_styles] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:range_styles]) + options[:range_styles].each do |x| styles = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(x[:styles]) @@ -184,6 +192,8 @@ def to_axlsx_package(opts={}, package=nil) end if options[:merges] + options[:merges] = SpreadsheetArchitect::Utils.hash_array_symbolize_keys(options[:merges]) + options[:merges].each do |x| if x[:range].is_a?(Hash) x[:range] = SpreadsheetArchitect::Utils::XLSX.range_hash_to_str(x[:range], max_row_length, num_rows) @@ -195,38 +205,41 @@ def to_axlsx_package(opts={}, package=nil) end end - if options[:freeze_headers] sheet.sheet_view.pane do |pane| - pane.top_left_cell = "A1" - pane.state = :frozen_split - pane.active_pane = :bottom_right + pane.state = :frozen pane.y_split = options[:headers].count end + elsif options[:freeze] + options[:freeze] = SpreadsheetArchitect::Utils.symbolize_keys(options[:freeze]) + sheet.sheet_view.pane do |pane| - pane.state = :frozen_split - pane.active_pane = :bottom_right - - if !options[:freeze][:columns] || options[:freeze][:columns] == :all - freeze_cell = "A" - elsif options[:freeze][:columns].is_a?(Range) - freeze_cell = "#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES[options[:freeze][:columns].first]}" - pane.y_split = options[:freeze][:columns].count + pane.state = :frozen + + ### Currently not working + #if options[:freeze][:active_pane] + # Axlsx.validate_pane_type(options[:freeze][:active_pane]) + # pane.active_pane = options[:freeze][:active_pane] + #else + # pane.active_pane = :bottom_right + #end + + if !options[:freeze][:rows] + raise SpreadsheetArchitect::Exceptions::ArgumentError.new("The :rows key must be specified in the :freeze option hash") + elsif options[:freeze][:rows].is_a?(Range) + pane.y_split = options[:freeze][:rows].count else - freeze_cell = "#{SpreadsheetArchitect::Utils::XLSX::COL_NAMES[options[:freeze][:columns]]}" pane.y_split = 1 end - if options[:freeze][:rows].is_a?(Range) - freeze_cell += "#{options[:freeze][:rows].first}" - pane.x_split = options[:freeze][:rows].count - else - freeze_cell += "#{options[:freeze][:rows]}" - pane.x_split = 1 + if options[:freeze][:columns] && options[:freeze][:columns] != :all + if options[:freeze][:columns].is_a?(Range) + pane.x_split = options[:freeze][:columns].count + else + pane.x_split = 1 + end end - - pane.top_left_cell = freeze_cell end end end diff --git a/lib/spreadsheet_architect/utils.rb b/lib/spreadsheet_architect/utils.rb index 174a700..9742ef6 100644 --- a/lib/spreadsheet_architect/utils.rb +++ b/lib/spreadsheet_architect/utils.rb @@ -216,6 +216,8 @@ def self.check_option_type(options, option_name, type) end def self.verify_option_types(options) + options = self.symbolize_keys(options, shallow: true) + check_option_type(options, :spreadsheet_columns, [Proc, Symbol, String]) check_option_type(options, :data, Array) check_option_type(options, :instances, Array) @@ -233,8 +235,9 @@ def self.verify_option_types(options) check_option_type(options, :freeze, Hash) end - def self.stringify_keys(hash={}) + def self.stringify_keys(hash) new_hash = {} + hash.each do |k,v| if v.is_a?(Hash) new_hash[k.to_s] = self.stringify_keys(v) @@ -242,7 +245,33 @@ def self.stringify_keys(hash={}) new_hash[k.to_s] = v end end + + return new_hash + end + + def self.symbolize_keys(hash, shallow: false) + new_hash = {} + + hash.each do |k,v| + if v.is_a?(Hash) + new_hash[k.to_sym] = shallow ? v : self.symbolize_keys(v) + else + new_hash[k.to_sym] = v + end + end + return new_hash end + + def self.hash_array_symbolize_keys(array) + new_array = [] + + array.each_with_index do |x,i| + new_array[i] = x.is_a?(Hash) ? self.symbolize_keys(x) : x + end + + return new_array + end + end end diff --git a/lib/spreadsheet_architect/utils/xlsx.rb b/lib/spreadsheet_architect/utils/xlsx.rb index 5554b3f..673162d 100644 --- a/lib/spreadsheet_architect/utils/xlsx.rb +++ b/lib/spreadsheet_architect/utils/xlsx.rb @@ -26,7 +26,8 @@ def self.get_type(value, type=nil) def self.convert_styles_to_axlsx(styles={}) styles = {} unless styles.is_a?(Hash) - styles = self.symbolize_keys(styles) + + styles = SpreadsheetArchitect::Utils.symbolize_keys(styles) if styles[:fg_color].nil? && styles[:color] && styles[:color].respond_to?(:sub) && !styles[:color].empty? styles[:fg_color] = styles.delete(:color).sub('#','') @@ -176,18 +177,6 @@ def self.conditional_styles_for_row(conditional_row_styles, row_index, row_data) private - def self.symbolize_keys(hash={}) - new_hash = {} - hash.each do |k, v| - if v.is_a?(Hash) - new_hash[k.to_sym] = self.symbolize_keys(v) - else - new_hash[k.to_sym] = v - end - end - new_hash - end - ### Limit of 16384 columns as per Excel limitations COL_NAMES = Array('A'..'XFD').freeze diff --git a/test/dummy_app/config/application.rb b/test/dummy_app/config/application.rb index 3c3d2fb..2e1eb0f 100644 --- a/test/dummy_app/config/application.rb +++ b/test/dummy_app/config/application.rb @@ -3,10 +3,7 @@ require 'rails/all' require 'axlsx' -silence_warnings do - ### required until monkey patch in axlsx_styler is updated to Ruby 2.0 prepend syntax - require 'axlsx_styler' -end +require 'axlsx_styler' Bundler.require diff --git a/test/unit/exceptions_test.rb b/test/unit/exceptions_test.rb index b6f8883..697f15f 100644 --- a/test/unit/exceptions_test.rb +++ b/test/unit/exceptions_test.rb @@ -23,11 +23,6 @@ class ExceptionsTest < ActiveSupport::TestCase assert_raise error do SpreadsheetArchitect::Utils.get_options({freeze: {rows: 1}, freeze_headers: true}, SpreadsheetArchitect) end - - assert_nothing_raised do - SpreadsheetArchitect.default_options = {freeze_headers: true} - SpreadsheetArchitect::Utils.get_options({freeze: {rows: 1}}, SpreadsheetArchitect) - end end test "NoDataError" do diff --git a/test/unit/kitchen_sink_test.rb b/test/unit/kitchen_sink_test.rb index 106f652..df751fc 100644 --- a/test/unit/kitchen_sink_test.rb +++ b/test/unit/kitchen_sink_test.rb @@ -20,7 +20,7 @@ def teardown end def test_xlsx - @options.merge!({ + opts = @options.merge({ column_styles: [ {columns: 0, styles: {bold: true}}, {columns: (1..3), styles: {color: "444444"}}, @@ -74,7 +74,7 @@ def test_xlsx }) # Using Array Data - file_data = SpreadsheetArchitect.to_xlsx(@options) + file_data = SpreadsheetArchitect.to_xlsx(opts) File.open(VERSIONED_BASE_PATH.join("kitchen_sink.xlsx"),'w+b') do |f| f.write file_data @@ -82,7 +82,7 @@ def test_xlsx end def test_ods - @options.merge!({ + opts = @options.merge({ headers: [ ['Latest Posts'], ['Title','Category','Author','Boolean','Posted on','Posted At'] @@ -100,7 +100,7 @@ def test_ods }) # Using Array Data - file_data = SpreadsheetArchitect.to_ods(@options) + file_data = SpreadsheetArchitect.to_ods(opts) File.open(VERSIONED_BASE_PATH.join("kitchen_sink.ods"),'w+b') do |f| f.write file_data diff --git a/test/unit/utils_test.rb b/test/unit/utils_test.rb index 545015f..4ddaa52 100644 --- a/test/unit/utils_test.rb +++ b/test/unit/utils_test.rb @@ -153,7 +153,7 @@ def teardown end test "stringify_keys" do - hash = klass.stringify_keys + hash = klass.stringify_keys({}) assert_empty hash hash = klass.stringify_keys({foo: :bar}) @@ -176,4 +176,35 @@ def teardown assert_nil hash[:foo] assert_equal hash['foo']['foo']['foo']['foo'], :bar end + + test "symbolize_keys" do + hash = klass.symbolize_keys({}) + assert_empty hash + + hash = klass.symbolize_keys({'foo' => :bar}) + assert_nil hash['foo'] + assert_equal hash[:foo], :bar + + hash = klass.symbolize_keys({'foo' => :bar, bar: :foo}) + assert_nil hash['foo'] + assert_equal hash[:foo], :bar + + hash = klass.symbolize_keys({'foo' => {'foo' => :bar}}) + assert_nil hash['foo'] + assert_equal hash[:foo][:foo], :bar + + hash = klass.symbolize_keys({'foo' => {'foo' => {'foo' => :bar}}}) + assert_nil hash['foo'] + assert_equal hash[:foo][:foo][:foo], :bar + end + + test "hash_array_symbolize_keys" do + array = klass.hash_array_symbolize_keys([]) + assert_empty array + + array = klass.hash_array_symbolize_keys([{'foo' => :bar}]) + assert_nil array[0]['foo'] + assert_equal array[0][:foo], :bar + end + end diff --git a/test/unit/xlsx_freeze_test.rb b/test/unit/xlsx_freeze_test.rb index b566a9a..a39c722 100644 --- a/test/unit/xlsx_freeze_test.rb +++ b/test/unit/xlsx_freeze_test.rb @@ -16,12 +16,12 @@ def teardown end def test_1 - @options.merge!({ + opts = @options.merge({ freeze: {rows: 1, columns: 1}, }) # Using Array Data - file_data = SpreadsheetArchitect.to_xlsx(@options) + file_data = SpreadsheetArchitect.to_xlsx(opts) File.open(VERSIONED_BASE_PATH.join("freeze_test_1.xlsx"),'w+b') do |f| f.write file_data @@ -29,42 +29,16 @@ def test_1 end def test_2 - @options.merge!({ + opts = @options.merge({ freeze: {rows: (2..4), columns: (2..4)}, }) # Using Array Data - file_data = SpreadsheetArchitect.to_xlsx(@options) + file_data = SpreadsheetArchitect.to_xlsx(opts) File.open(VERSIONED_BASE_PATH.join("freeze_test_2.xlsx"),'w+b') do |f| f.write file_data end end - def test_ods - @options.merge!({ - headers: [ - ['Latest Posts'], - ['Title','Category','Author','Boolean','Posted on','Posted At'] - ], - data: 50.times.map{|i| [i, "foobar-#{i}", (5.4*i), true, Date.today, Time.now]}, - column_types: [ - :string, - :float, - :float, - :boolean, - :date, - :time, - nil - ], - }) - - # Using Array Data - file_data = SpreadsheetArchitect.to_ods(@options) - - File.open(VERSIONED_BASE_PATH.join("kitchen_sink.ods"),'w+b') do |f| - f.write file_data - end - end - end diff --git a/test/unit/xlsx_utils_test.rb b/test/unit/xlsx_utils_test.rb index cce1f3b..4dfde12 100644 --- a/test/unit/xlsx_utils_test.rb +++ b/test/unit/xlsx_utils_test.rb @@ -113,27 +113,6 @@ def teardown #end end - test "symbolize_keys" do - hash = klass.symbolize_keys - assert_empty hash - - hash = klass.symbolize_keys({'foo' => :bar}) - assert_nil hash['foo'] - assert_equal hash[:foo], :bar - - hash = klass.symbolize_keys({'foo' => :bar, bar: :foo}) - assert_nil hash['foo'] - assert_equal hash[:foo], :bar - - hash = klass.symbolize_keys({'foo' => {'foo' => :bar}}) - assert_nil hash['foo'] - assert_equal hash[:foo][:foo], :bar - - hash = klass.symbolize_keys({'foo' => {'foo' => {'foo' => :bar}}}) - assert_nil hash['foo'] - assert_equal hash[:foo][:foo][:foo], :bar - end - test "constants" do assert_equal klass::COL_NAMES.count, 16384 assert_equal klass::COL_NAMES.first, 'A'