Skip to content

Commit

Permalink
Improve argument handling for :freeze option to support all axlsx opt…
Browse files Browse the repository at this point in the history
…ions (#40)
  • Loading branch information
westonganger committed Mar 15, 2022
1 parent 4cd40cb commit 851f1cd
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -224,7 +224,7 @@ See this file for more details: [test/unit/multi_sheet_test.rb](./test/unit/mult
|**column_types**<br>*Array*||Valid types for XLSX are :string, :integer, :float, :date, :time, :boolean, nil = auto determine.|
|**column_widths**<br>*Array*||Sometimes you may want explicit column widths. Use nil if you want a column to autofit again.|
|**freeze_headers**<br>*Boolean*||Make all header rows frozen/fixed so they do not scroll.|
|**freeze**<br>*Hash*|`{rows: (1..4), columns: :all}`|Make all specified rows and columns frozen/fixed so they do not scroll.|
|**freeze**<br>*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**<br>*Boolean*|`false`|Removes defaults and default styles. Particularily useful for heavily customized spreadsheets where the default styles get in the way.|
|**escape_formulas**<br>*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**<br>*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.|
Expand Down
75 changes: 50 additions & 25 deletions lib/spreadsheet_architect/class_methods/xlsx.rb
Expand Up @@ -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])

Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions lib/spreadsheet_architect/utils.rb
Expand Up @@ -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

Expand Down
59 changes: 55 additions & 4 deletions test/unit/xlsx_freeze_test.rb
Expand Up @@ -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: [
Expand All @@ -15,28 +21,73 @@ def setup
def teardown
end

def test_1
def test_basic
opts = @options.merge({
freeze: {rows: 1, columns: 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)},
})

# 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
Expand Down

0 comments on commit 851f1cd

Please sign in to comment.