From 851f1cd043afc375b887e9687ff8d9103097744e Mon Sep 17 00:00:00 2001 From: Weston Ganger Date: Mon, 14 Mar 2022 22:29:49 -0700 Subject: [PATCH] Improve argument handling for :freeze option to support all axlsx options (#40) --- CHANGELOG.md | 1 + README.md | 2 +- .../class_methods/xlsx.rb | 75 ++++++++++++------- lib/spreadsheet_architect/utils.rb | 8 +- test/unit/xlsx_freeze_test.rb | 59 ++++++++++++++- 5 files changed, 113 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17accc3..c3be449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG - **5.0.0 - Unreleased** - [View Diff](https://github.com/westonganger/spreadsheet_architect/compare/v4.2.0...master) - **Breaking Change** - [PR #38](https://github.com/westonganger/spreadsheet_architect/pull/38) - Add `escape_formulas` option for xlsx spreadsheets. This is a breaking change because we default to `escape_formulas: true` whereas before there was no formula escaping at all. The reasoning for this breaking change is that creating spreadsheets where many of the fields contain direct user input are a large majority compared to use cases that involve formulas. - Add option `use_zero_based_row_index: true` (Default `false`) which allows you to use zero-based row indexes instead of the default 1-based row indexes. Recomended to set this option for the whole project. The original reason it was designed to be 1-based is because spreadsheet row numbers literally start with 1. However this tends to be unituitive for the developer because columns use zero based indexes because they use letter-based notation instead. + - Improve argument handling for freeze option and add support for all Axlsx supported options for panes using the `:freeze` hash. See test case for example (./test/unit/xlsx_freeze_test.rb) - **4.2.0** - May 27, 2021 - [View Diff](https://github.com/westonganger/spreadsheet_architect/compare/v4.1.0...v4.2.0) - Add option `:skip_defaults` which removes the defaults and default styles. Particularily useful for heavily customized spreadsheets where the default styles get in the way. diff --git a/README.md b/README.md index cf22374..58bca73 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ See this file for more details: [test/unit/multi_sheet_test.rb](./test/unit/mult |**column_types**
*Array*||Valid types for XLSX are :string, :integer, :float, :date, :time, :boolean, nil = auto determine.| |**column_widths**
*Array*||Sometimes you may want explicit column widths. Use nil if you want a column to autofit again.| |**freeze_headers**
*Boolean*||Make all header rows frozen/fixed so they do not scroll.| -|**freeze**
*Hash*|`{rows: (1..4), columns: :all}`|Make all specified rows and columns frozen/fixed so they do not scroll.| +|**freeze**
*Hash*||Make all specified row and/or column frozen/fixed so they do not scroll. See [example usage](./test/unit/xlsx_freeze_test.rb)| |**skip_defaults**
*Boolean*|`false`|Removes defaults and default styles. Particularily useful for heavily customized spreadsheets where the default styles get in the way.| |**escape_formulas**
*Boolean* or *Array*|`true`|Pass a single boolean to apply to all cells, or an array of booleans to control column-by-column. Advisable to be set true when involved with untrusted user input. See [an example of the underlying functionality](https://github.com/caxlsx/caxlsx/blob/master/examples/escape_formula_example.md). NOTE: Header row cells are not escaped. | |**use_zero_based_row_index**
*Boolean*|`false`|Allows you to use zero-based row indexes when defining `range_styles`, `merges`, etc. Recomended to set this option for the whole project rather than per call. The original reason it was designed to be 1-based is because spreadsheet row numbers actually start with 1.| diff --git a/lib/spreadsheet_architect/class_methods/xlsx.rb b/lib/spreadsheet_architect/class_methods/xlsx.rb index 7315d3f..29e7b60 100644 --- a/lib/spreadsheet_architect/class_methods/xlsx.rb +++ b/lib/spreadsheet_architect/class_methods/xlsx.rb @@ -14,10 +14,6 @@ def to_axlsx_package(opts={}, package=nil) opts = SpreadsheetArchitect::Utils.get_options(opts, self) options = SpreadsheetArchitect::Utils.get_cell_data(opts, self) - if options[:column_types] && !(options[:column_types].compact.collect(&:to_sym) - SpreadsheetArchitect::XLSX_COLUMN_TYPES).empty? - raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Invalid column type. Valid XLSX values are #{SpreadsheetArchitect::XLSX_COLUMN_TYPES}") - end - header_style = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(options[:header_style]) row_style = SpreadsheetArchitect::Utils::XLSX.convert_styles_to_axlsx(options[:row_style]) @@ -212,33 +208,62 @@ def to_axlsx_package(opts={}, package=nil) end elsif options[:freeze] - options[:freeze] = SpreadsheetArchitect::Utils.symbolize_keys(options[:freeze]) + case options[:freeze][:type].to_s + when "split_panes" + options[:freeze][:state] == "split" + when "frozen", "freeze" + options[:freeze][:state] == "frozen" + end + + if options[:freeze][:rows] + options[:freeze][:row] ||= options[:freeze][:rows] + end + + if options[:freeze][:columns] + options[:freeze][:column] ||= options[:freeze][:columns] + end + + if options[:freeze][:row] == :all + options[:freeze][:row] = nil + elsif options[:freeze][:row].is_a?(Range) + options[:freeze][:row] = options[:freeze][:row].last + end + + if options[:freeze][:column] == :all + options[:freeze][:column] = nil + elsif options[:freeze][:column].is_a?(Range) + options[:freeze][:column] = options[:freeze][:column].last + end + + if !options[:freeze][:row] && !options[:freeze][:column] + raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Missing required :row or :column value in the :freeze option hash") + elsif options[:freeze][:row] && !options[:freeze][:row].is_a?(Integer) + raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Invalid :row value provided for in :freeze option hash, must be an Integer") + elsif options[:freeze][:column] && !options[:freeze][:column].is_a?(Integer) + raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Invalid :column value provided for in :freeze option hash, must be an Integer") + end sheet.sheet_view.pane do |pane| - pane.state = :frozen + pane.state = (options[:freeze][:state] || :frozen).to_sym ### Other options are :split and :frozen_split - ### 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 - pane.y_split = 1 + if options[:freeze][:active_pane] + pane.active_pane = options[:freeze][:active_pane].to_sym end - 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 + if options[:freeze][:top_left_cell] + pane.top_left_cell = options[:freeze][:top_left_cell] + end + + if options[:freeze][:row] + if options[:use_zero_based_row_index] + options[:freeze][:row] += 1 end + + pane.y_split = options[:freeze][:row] + end + + if options[:freeze][:column] + pane.x_split = options[:freeze][:column] end end end diff --git a/lib/spreadsheet_architect/utils.rb b/lib/spreadsheet_architect/utils.rb index 279e3d1..e9888c1 100644 --- a/lib/spreadsheet_architect/utils.rb +++ b/lib/spreadsheet_architect/utils.rb @@ -142,11 +142,15 @@ def self.get_options(options, klass) end end + if options[:column_types] && !(options[:column_types].compact.collect(&:to_sym) - SpreadsheetArchitect::XLSX_COLUMN_TYPES).empty? + raise SpreadsheetArchitect::Exceptions::ArgumentError.new("Invalid column type. Valid XLSX values are #{SpreadsheetArchitect::XLSX_COLUMN_TYPES}") + end + if options[:freeze] + options[:freeze] = SpreadsheetArchitect::Utils.symbolize_keys(options[:freeze]) + if options[:freeze_headers] raise SpreadsheetArchitect::Exceptions::ArgumentError.new('Cannot use both :freeze and :freeze_headers options at the same time') - elsif options[:freeze].is_a?(Hash) && !options[:freeze][:rows] - raise SpreadsheetArchitect::Exceptions::ArgumentError.new('Must provide a :rows key when passing a hash to the :freeze option') end end diff --git a/test/unit/xlsx_freeze_test.rb b/test/unit/xlsx_freeze_test.rb index a39c722..c45024a 100644 --- a/test/unit/xlsx_freeze_test.rb +++ b/test/unit/xlsx_freeze_test.rb @@ -2,6 +2,12 @@ class XlsxFreezeTest < ActiveSupport::TestCase + def base_path + path = VERSIONED_BASE_PATH.join("xlsx/freeze/") + FileUtils.mkdir_p(path) + return path + end + def setup @options = { headers: [ @@ -15,7 +21,7 @@ def setup def teardown end - def test_1 + def test_basic opts = @options.merge({ freeze: {rows: 1, columns: 1}, }) @@ -23,12 +29,12 @@ def test_1 # Using Array Data file_data = SpreadsheetArchitect.to_xlsx(opts) - File.open(VERSIONED_BASE_PATH.join("freeze_test_1.xlsx"),'w+b') do |f| + File.open(base_path.join("freeze_#{__method__}.xlsx"),'w+b') do |f| f.write file_data end end - def test_2 + def test_using_ranges opts = @options.merge({ freeze: {rows: (2..4), columns: (2..4)}, }) @@ -36,7 +42,52 @@ def test_2 # Using Array Data file_data = SpreadsheetArchitect.to_xlsx(opts) - File.open(VERSIONED_BASE_PATH.join("freeze_test_2.xlsx"),'w+b') do |f| + File.open(base_path.join("freeze_#{__method__}.xlsx"),'w+b') do |f| + f.write file_data + end + end + + def test_using_legacy_arguments + opts = @options.merge({ + freeze: {rows: :all, columns: 2}, + }) + + # Using Array Data + file_data = SpreadsheetArchitect.to_xlsx(opts) + + File.open(base_path.join("freeze_#{__method__}.xlsx"),'w+b') do |f| + f.write file_data + end + end + + def test_freeze_type + opts = @options.merge({ + freeze: {row: (@options[:data].size-2), column: 16, type: "split_panes"}, + }) + + # Using Array Data + file_data = SpreadsheetArchitect.to_xlsx(opts) + + File.open(base_path.join("freeze_#{__method__}.xlsx"),'w+b') do |f| + f.write file_data + end + end + + def test_panes_all_axlsx_options + opts = @options.merge({ + freeze: { + row: (@options[:data].size-2), + column: 16, + state: "split", + #active_pane: "top_right", + #top_left_cell: "A2", + }, + }) + + # Using Array Data + file_data = SpreadsheetArchitect.to_xlsx(opts) + + File.open(base_path.join("freeze_#{__method__}.xlsx"),'w+b') do |f| f.write file_data end end