From e73bd501eb15106daeb4cf9830991a8181835b06 Mon Sep 17 00:00:00 2001 From: Randy Morgan Date: Sun, 23 Sep 2012 11:20:37 +0900 Subject: Autofilter extended implementation Beginnings of 18.3.2 AutoFilter Settings implementation. I've moved auto_filter into its own directory as there will be a minimum of 15 or 20 classes required to implement this in full. --- README.md | 6 ++ lib/axlsx.rb | 1 + lib/axlsx/workbook/workbook.rb | 2 +- lib/axlsx/workbook/worksheet/auto_filter.rb | 35 ----------- .../workbook/worksheet/auto_filter/auto_filter.rb | 58 ++++++++++++++++++ .../worksheet/auto_filter/filter_column.rb | 70 ++++++++++++++++++++++ .../workbook/worksheet/auto_filter/filters.rb | 14 +++++ lib/axlsx/workbook/worksheet/table_style_info.rb | 2 +- test/workbook/worksheet/table/tc_table.rb | 68 --------------------- .../worksheet/table/tc_table_style_info.rb | 53 ---------------- test/workbook/worksheet/tc_auto_filter.rb | 32 ++++++++++ test/workbook/worksheet/tc_cell.rb | 1 - test/workbook/worksheet/tc_table.rb | 68 +++++++++++++++++++++ test/workbook/worksheet/tc_table_style_info.rb | 53 ++++++++++++++++ test/workbook/worksheet/tc_worksheet.rb | 6 +- 15 files changed, 307 insertions(+), 162 deletions(-) delete mode 100644 lib/axlsx/workbook/worksheet/auto_filter.rb create mode 100644 lib/axlsx/workbook/worksheet/auto_filter/auto_filter.rb create mode 100644 lib/axlsx/workbook/worksheet/auto_filter/filter_column.rb create mode 100644 lib/axlsx/workbook/worksheet/auto_filter/filters.rb delete mode 100644 test/workbook/worksheet/table/tc_table.rb delete mode 100644 test/workbook/worksheet/table/tc_table_style_info.rb create mode 100644 test/workbook/worksheet/tc_auto_filter.rb create mode 100644 test/workbook/worksheet/tc_table.rb create mode 100644 test/workbook/worksheet/tc_table_style_info.rb diff --git a/README.md b/README.md index ed421766..fd83e413 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,12 @@ This gem has 100% test coverage using test/unit. To execute tests for this gem, #Change log --------- +- **September.??.12**: 1.2.4 + - Improved support for autowidth when custom styles are applied + - Added support for table style info that lets you take advantage of + all the predefined table styles. + - Improved style management for fonts so they merge undefined values + from the initial master. - **September.8.12**: 1.2.3 - enhance exponential float/bigdecimal values rendering as strings intead of 'numbers' in excel. diff --git a/lib/axlsx.rb b/lib/axlsx.rb index 50f34c70..85a7b7c9 100644 --- a/lib/axlsx.rb +++ b/lib/axlsx.rb @@ -95,6 +95,7 @@ module Axlsx # @param [String] s The snake case string to camelize # @return [String] def self.camel(s="", all_caps = true) + s = s.to_s s = s.capitalize if all_caps s.gsub(/_(.)/){ $1.upcase } end diff --git a/lib/axlsx/workbook/workbook.rb b/lib/axlsx/workbook/workbook.rb index 3ff61c07..cdc73360 100644 --- a/lib/axlsx/workbook/workbook.rb +++ b/lib/axlsx/workbook/workbook.rb @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- module Axlsx -require 'axlsx/workbook/worksheet/auto_filter.rb' +require 'axlsx/workbook/worksheet/auto_filter/auto_filter.rb' require 'axlsx/workbook/worksheet/date_time_converter.rb' require 'axlsx/workbook/worksheet/protected_range.rb' require 'axlsx/workbook/worksheet/protected_ranges.rb' diff --git a/lib/axlsx/workbook/worksheet/auto_filter.rb b/lib/axlsx/workbook/worksheet/auto_filter.rb deleted file mode 100644 index 7d9fbcfc..00000000 --- a/lib/axlsx/workbook/worksheet/auto_filter.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Axlsx - - #This class represents an auto filter range in a worksheet - class AutoFilter - - # creates a new Autofilter object - # @param [Worksheet] worksheet - def initialize(worksheet) - raise ArgumentError, 'you must provide a worksheet' unless worksheet.is_a?(Worksheet) - @worksheet = worksheet - end - - attr_reader :worksheet - - # The range the autofilter should be applied to. - # This should be a string like 'A1:B8' - # @return [String] - attr_accessor :range - - # the formula for the defined name required for this auto filter - # @return [String] - def defined_name - return unless range - Axlsx.cell_range(range.split(':').collect { |name| worksheet.name_to_cell(name)}) - end - - # serialize the object - # @return [String] - def to_xml_string(str='') - return unless range - str << "" - end - - end -end diff --git a/lib/axlsx/workbook/worksheet/auto_filter/auto_filter.rb b/lib/axlsx/workbook/worksheet/auto_filter/auto_filter.rb new file mode 100644 index 00000000..a2e82835 --- /dev/null +++ b/lib/axlsx/workbook/worksheet/auto_filter/auto_filter.rb @@ -0,0 +1,58 @@ + +require 'axlsx/workbook/worksheet/auto_filter/filter_column.rb' +require 'axlsx/workbook/worksheet/auto_filter/filters.rb' + +module Axlsx + + #This class represents an auto filter range in a worksheet + class AutoFilter + + # creates a new Autofilter object + # @param [Worksheet] worksheet + def initialize(worksheet) + raise ArgumentError, 'you must provide a worksheet' unless worksheet.is_a?(Worksheet) + @worksheet = worksheet + end + + attr_reader :worksheet + + # The range the autofilter should be applied to. + # This should be a string like 'A1:B8' + # @return [String] + attr_accessor :range + + # the formula for the defined name required for this auto filter + # This prepends the worksheet name to the absolute cell reference + # e.g. A1:B2 -> 'Sheet1'!$A$1:$B$2 + # @return [String] + def defined_name + return unless range + Axlsx.cell_range(range.split(':').collect { |name| worksheet.name_to_cell(name)}) + end + + # A collection of filterColumns for this auto_filter + # @return [SimpleTypedList] + def columns + @columns ||= SimpleTypedList.new FilterColumn + end + + # Adds a filter column. This is the recommended way to create and manage filter columns for your autofilter. + # In addition to the require id and type parameters, options will be passed to the filter column during instantiation. + # @param [String] col_id Zero-based index indicating the AutoFilter column to which this filter information applies. + # @param [Symbol] filter_type A symbol representing one of the supported filter types. + # @param [Hash] options a hash of options to pass into the generated filter + # @return [FilterColumn] + def add_column(col_id, filter_type, options = {}) + columns << FilterColumn.new(col_id, filter_type, options) + columns.last + end + + # serialize the object + # @return [String] + def to_xml_string(str='') + return unless range + str << "" + end + + end +end diff --git a/lib/axlsx/workbook/worksheet/auto_filter/filter_column.rb b/lib/axlsx/workbook/worksheet/auto_filter/filter_column.rb new file mode 100644 index 00000000..57f7e23b --- /dev/null +++ b/lib/axlsx/workbook/worksheet/auto_filter/filter_column.rb @@ -0,0 +1,70 @@ +module Axlsx + # The filterColumn collection identifies a particular column in the AutoFilter + # range and specifies filter information that has been applied to this column. + # If a column in the AutoFilter range has no criteria specified, + # then there is no corresponding filterColumn collection expressed for that column. + class FilterColumn + + FILTERS = [:filters, :top10, :custom_filters, :dynamic_filters, :color_filters, :icon_filters] + # Zero-based index indicating the AutoFilter column to which this filter information applies. + # @return [Integer] + attr_reader :col_id + + # Flag indicating whether the AutoFilter button for this column is hidden. + # @return [Boolean] + attr_reader :hidden_button + + # Flag indicating whether the filter button is visible. + # When the cell containing the filter button is merged with another cell, + # the filter button can be hidden, and not drawn. + # @return [Boolean] + attr_reader :show_button + + # Filter information applied to the column + # @return [Filters|Top10|CustomFilters|dynamicFilters|colorFilters|iconFilters] The filter + # information for this filter columns. + attr_reader :filter + + # Creates a new FilterColumn object + # @note This class yeilds its filter object as that is where the vast majority of processing will be done + # @param [Integer|Cell] col_id The zero based index for the column to which this filter will be applied + # @param [Symbol] filter_type The symbolized class name of the filter to apply to this column. + # @param [Hash] options options for this object and the filter + # @option [Boolean] hidden_button @see hidden_button + # @option [Boolean] show_button @see show_button + + def initialize(col_id, filter_type, options = {}) + RestrictionValidator.validate 'FilterColumn.filter', FILTERS, filter_type + self.col_id = col_id + options.each do |o| + self.send("#{o[0]}=", o[1]) if self.respond_to? "#{o[0]}=" + end + @filter = Axlsx.const_get(Axlsx.camel(filter_type)) + yield @filter if block_given? + end + + # Sets the col_id attribute for this filter column. + # @param [Integer | Cell] column_index The zero based index of the column to which this filter applies. + # When you specify a cell, the column index will be read off the cell + # @return [Integer] + def col_id=(column_index) + column_index = column_index.col if column_index.is_a?(Cell) + Axlsx.validate_unsigned_int column_index + @col_id = column_index + end + + # Sets the button_hidden attribute for this filter column + # @param [Boolean] hidden Flag indicating whether the AutoFilter button for this column is hidden. + # @return [Boolean] + def button_hidden=(hidden) + DataValidater.validate_boolean hidden + @button_hidden = hidden + end + + # Serialize the object to xml + def to_xml_string(str='') + + end + + end +end diff --git a/lib/axlsx/workbook/worksheet/auto_filter/filters.rb b/lib/axlsx/workbook/worksheet/auto_filter/filters.rb new file mode 100644 index 00000000..f37e2f29 --- /dev/null +++ b/lib/axlsx/workbook/worksheet/auto_filter/filters.rb @@ -0,0 +1,14 @@ +module Axlsx + + class Filters + + def initialize(options={}) + options[:filter_items].each do |filter| + @filters << Filter.new(filter) + end + options[:date_group_items].each do |date_group| + @date_group_items << DateGroupItem.new(date_group) + end + end + end +end diff --git a/lib/axlsx/workbook/worksheet/table_style_info.rb b/lib/axlsx/workbook/worksheet/table_style_info.rb index f0d08d8b..778546e0 100644 --- a/lib/axlsx/workbook/worksheet/table_style_info.rb +++ b/lib/axlsx/workbook/worksheet/table_style_info.rb @@ -20,7 +20,7 @@ module Axlsx # @see Annex G. (normative) Predefined SpreadsheetML Style Definitions in part 1 of the specification. def initialize(options = {}) initialize_defaults - name= 'TableStyleMedium9' + @name = 'TableStyleMedium9' options.each do |k, v| send("#{k}=", v) if respond_to? "#{k}=" end diff --git a/test/workbook/worksheet/table/tc_table.rb b/test/workbook/worksheet/table/tc_table.rb deleted file mode 100644 index de86b886..00000000 --- a/test/workbook/worksheet/table/tc_table.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'tc_helper.rb' - -class TestTable < Test::Unit::TestCase - def setup - p = Axlsx::Package.new - @ws = p.workbook.add_worksheet - 40.times do - @ws << ["aa","aa","aa","aa","aa","aa"] - end - end - - def test_initialization - assert(@ws.workbook.tables.empty?) - assert(@ws.tables.empty?) - - end - - def test_table_style_info - table = @ws.add_table('A1:D5', :name => 'foo', :style_info => { :show_row_stripes => true, :name => "TableStyleMedium25" }) - assert_equal('TableStyleMedium25', table.table_style_info.name) - assert_equal(true, table.table_style_info.show_row_stripes) - end - - def test_add_table - name = "test" - table = @ws.add_table("A1:D5", :name => name) - assert(table.is_a?(Axlsx::Table), "must create a table") - assert_equal(@ws.workbook.tables.last, table, "must be added to workbook table collection") - assert_equal(@ws.tables.last, table, "must be added to worksheet table collection") - assert_equal(table.name, name, "options for name are applied") - end - - def test_pn - @ws.add_table("A1:D5") - assert_equal(@ws.tables.first.pn, "tables/table1.xml") - end - - def test_rId - @ws.add_table("A1:D5") - assert_equal(@ws.tables.first.rId, "rId1") - end - - def test_index - @ws.add_table("A1:D5") - assert_equal(@ws.tables.first.index, @ws.workbook.tables.index(@ws.tables.first)) - end - - def test_relationships - assert(@ws.relationships.empty?) - table = @ws.add_table("A1:D5") - assert_equal(@ws.relationships.size, 1, "adding a table adds a relationship") - table = @ws.add_table("F1:J5") - assert_equal(@ws.relationships.size, 2, "adding a table adds a relationship") - end - - def test_to_xml_string - table = @ws.add_table("A1:D5") - schema = Nokogiri::XML::Schema(File.open(Axlsx::SML_XSD)) - doc = Nokogiri::XML(table.to_xml_string) - errors = [] - schema.validate(doc).each do |error| - errors.push error - puts error.message - end - assert(errors.empty?, "error free validation") - end - -end diff --git a/test/workbook/worksheet/table/tc_table_style_info.rb b/test/workbook/worksheet/table/tc_table_style_info.rb deleted file mode 100644 index c0c452c9..00000000 --- a/test/workbook/worksheet/table/tc_table_style_info.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'tc_helper.rb' - -class TestTableStyleInfo < Test::Unit::TestCase - def setup - p = Axlsx::Package.new - @ws = p.workbook.add_worksheet - 40.times do - @ws.add_row %w(aa bb cc dd ee ff gg hh ii jj kk) - end - @table = @ws.add_table(Axlsx::cell_range([@ws.rows.first.cells.first,@ws.rows.last.cells.last], false), :name => 'foo') - @options = { :show_first_column => 1, - :show_last_column => 1, - :show_row_stripes => 1, - :show_column_stripes => 1, - :name => "TableStyleDark4" } - - - end - - def test_initialize - table_style = Axlsx::TableStyleInfo.new @options - @options.each do |key, value| - assert_equal(value, table_style.send(key.to_sym)) - end - end - - def test_boolean_properties - table_style = Axlsx::TableStyleInfo.new - @options.keys.each do |key| - assert_nothing_raised { table_style.send("#{key.to_sym}=", true) } - assert_raises(ArgumentError) { table_style.send(key.to_sym, 'foo') } - end - end - def doc - @doc ||= Nokogiri::XML(Axlsx::TableStyleInfo.new(@options).to_xml_string) - end - - def test_to_xml_string_first_column - assert(doc.xpath('//tableStyleInfo[@showLastColumn=1]')) - end - - def test_to_xml_string_row_stripes - assert(doc.xpath('//tableStyleInfo[@showRowStripes=1]')) - end - - def test_to_xml_string_column_stripes - assert(doc.xpath('//tableStyleInfo[@showColumnStripes=1]')) - end - - def test_to_xml_string_name - assert(doc.xpath("//tableStyleInfo[@name=#{@options[:name]}]")) - end -end diff --git a/test/workbook/worksheet/tc_auto_filter.rb b/test/workbook/worksheet/tc_auto_filter.rb new file mode 100644 index 00000000..77ee2b2c --- /dev/null +++ b/test/workbook/worksheet/tc_auto_filter.rb @@ -0,0 +1,32 @@ +require 'tc_helper.rb' + +class TestAutoFilter < Test::Unit::TestCase + + def setup + ws = Axlsx::Package.new.workbook.add_worksheet + 3.times { ws.add_row [1,2,3] } + @auto_filter = ws.auto_filter + @auto_filter.range = 'A1:C3' + end + + def test_defined_name + assert_equal("'Sheet1'!$A$1:$C$3", @auto_filter.defined_name) + end + + def test_to_xml_string + doc = Nokogiri::XML(@auto_filter.to_xml_string) + assert(doc.xpath("autoFilter[@ref='#{@auto_filter.range}']")) + end + + def test_columns + assert @auto_filter.columns.is_a?(Axlsx::SimpleTypedList) + assert_equal @auto_filter.columns.allowed_types, [Axlsx::FilterColumn] + end + + def test_add_column + @auto_filter.add_column(0, :filters) do |column| + assert column.is_a? FilterColumn + end + end + +end diff --git a/test/workbook/worksheet/tc_cell.rb b/test/workbook/worksheet/tc_cell.rb index 7188bc1d..0b332d86 100644 --- a/test/workbook/worksheet/tc_cell.rb +++ b/test/workbook/worksheet/tc_cell.rb @@ -277,7 +277,6 @@ class TestCell < Test::Unit::TestCase def test_font_size_with_bolding @c.style = @c.row.worksheet.workbook.styles.add_style :b => true - sz = @c.send(:font_size) assert_equal(@c.row.worksheet.workbook.styles.fonts.first.sz * 1.5, @c.send(:font_size)) end diff --git a/test/workbook/worksheet/tc_table.rb b/test/workbook/worksheet/tc_table.rb new file mode 100644 index 00000000..de86b886 --- /dev/null +++ b/test/workbook/worksheet/tc_table.rb @@ -0,0 +1,68 @@ +require 'tc_helper.rb' + +class TestTable < Test::Unit::TestCase + def setup + p = Axlsx::Package.new + @ws = p.workbook.add_worksheet + 40.times do + @ws << ["aa","aa","aa","aa","aa","aa"] + end + end + + def test_initialization + assert(@ws.workbook.tables.empty?) + assert(@ws.tables.empty?) + + end + + def test_table_style_info + table = @ws.add_table('A1:D5', :name => 'foo', :style_info => { :show_row_stripes => true, :name => "TableStyleMedium25" }) + assert_equal('TableStyleMedium25', table.table_style_info.name) + assert_equal(true, table.table_style_info.show_row_stripes) + end + + def test_add_table + name = "test" + table = @ws.add_table("A1:D5", :name => name) + assert(table.is_a?(Axlsx::Table), "must create a table") + assert_equal(@ws.workbook.tables.last, table, "must be added to workbook table collection") + assert_equal(@ws.tables.last, table, "must be added to worksheet table collection") + assert_equal(table.name, name, "options for name are applied") + end + + def test_pn + @ws.add_table("A1:D5") + assert_equal(@ws.tables.first.pn, "tables/table1.xml") + end + + def test_rId + @ws.add_table("A1:D5") + assert_equal(@ws.tables.first.rId, "rId1") + end + + def test_index + @ws.add_table("A1:D5") + assert_equal(@ws.tables.first.index, @ws.workbook.tables.index(@ws.tables.first)) + end + + def test_relationships + assert(@ws.relationships.empty?) + table = @ws.add_table("A1:D5") + assert_equal(@ws.relationships.size, 1, "adding a table adds a relationship") + table = @ws.add_table("F1:J5") + assert_equal(@ws.relationships.size, 2, "adding a table adds a relationship") + end + + def test_to_xml_string + table = @ws.add_table("A1:D5") + schema = Nokogiri::XML::Schema(File.open(Axlsx::SML_XSD)) + doc = Nokogiri::XML(table.to_xml_string) + errors = [] + schema.validate(doc).each do |error| + errors.push error + puts error.message + end + assert(errors.empty?, "error free validation") + end + +end diff --git a/test/workbook/worksheet/tc_table_style_info.rb b/test/workbook/worksheet/tc_table_style_info.rb new file mode 100644 index 00000000..c0c452c9 --- /dev/null +++ b/test/workbook/worksheet/tc_table_style_info.rb @@ -0,0 +1,53 @@ +require 'tc_helper.rb' + +class TestTableStyleInfo < Test::Unit::TestCase + def setup + p = Axlsx::Package.new + @ws = p.workbook.add_worksheet + 40.times do + @ws.add_row %w(aa bb cc dd ee ff gg hh ii jj kk) + end + @table = @ws.add_table(Axlsx::cell_range([@ws.rows.first.cells.first,@ws.rows.last.cells.last], false), :name => 'foo') + @options = { :show_first_column => 1, + :show_last_column => 1, + :show_row_stripes => 1, + :show_column_stripes => 1, + :name => "TableStyleDark4" } + + + end + + def test_initialize + table_style = Axlsx::TableStyleInfo.new @options + @options.each do |key, value| + assert_equal(value, table_style.send(key.to_sym)) + end + end + + def test_boolean_properties + table_style = Axlsx::TableStyleInfo.new + @options.keys.each do |key| + assert_nothing_raised { table_style.send("#{key.to_sym}=", true) } + assert_raises(ArgumentError) { table_style.send(key.to_sym, 'foo') } + end + end + def doc + @doc ||= Nokogiri::XML(Axlsx::TableStyleInfo.new(@options).to_xml_string) + end + + def test_to_xml_string_first_column + assert(doc.xpath('//tableStyleInfo[@showLastColumn=1]')) + end + + def test_to_xml_string_row_stripes + assert(doc.xpath('//tableStyleInfo[@showRowStripes=1]')) + end + + def test_to_xml_string_column_stripes + assert(doc.xpath('//tableStyleInfo[@showColumnStripes=1]')) + end + + def test_to_xml_string_name + assert(doc.xpath("//tableStyleInfo[@name=#{@options[:name]}]")) + end +end diff --git a/test/workbook/worksheet/tc_worksheet.rb b/test/workbook/worksheet/tc_worksheet.rb index a57fa811..0dcb6756 100644 --- a/test/workbook/worksheet/tc_worksheet.rb +++ b/test/workbook/worksheet/tc_worksheet.rb @@ -246,7 +246,7 @@ class TestWorksheet < Test::Unit::TestCase def test_to_xml_string_auto_filter @ws.add_row [1, "two"] - @ws.auto_filter = "A1:B1" + @ws.auto_filter.range = "A1:B1" doc = Nokogiri::XML(@ws.to_xml_string) assert_equal(doc.xpath('//xmlns:worksheet/xmlns:autoFilter[@ref="A1:B1"]').size, 1) end @@ -337,7 +337,7 @@ class TestWorksheet < Test::Unit::TestCase @ws.page_margins.set :left => 9 @ws.page_setup.set :fit_to_width => 1 @ws.print_options.set :headings => true - @ws.auto_filter = "A1:C3" + @ws.auto_filter.range = "A1:C3" @ws.merge_cells "A4:A5" @ws.add_chart Axlsx::Pie3DChart @ws.add_table "E1:F3" @@ -424,7 +424,7 @@ class TestWorksheet < Test::Unit::TestCase def test_auto_filter assert(@ws.auto_filter.range.nil?) assert_raise(ArgumentError) { @ws.auto_filter = 123 } - @ws.auto_filter = "A1:D9" + @ws.auto_filter.range = "A1:D9" assert_equal(@ws.auto_filter.range, "A1:D9") end end -- cgit v1.2.3