lib/write_xlsx/package/table.rb in write_xlsx-0.58.0 vs lib/write_xlsx/package/table.rb in write_xlsx-0.59.0

- old
+ new

@@ -6,17 +6,53 @@ module Writexlsx module Package class Table include Writexlsx::Utility - attr_writer :properties + class ColumnData + attr_reader :id + attr_accessor :name, :format, :formula + attr_accessor :total_string, :total_function - def initialize - @writer = Package::XMLWriterSimple.new - @properties = {} + def initialize(id, param = {}) + @id = id + @name = "Column#{id}" + @total_string = '' + @total_function = '' + @formula = '' + @format = nil + @user_data = param[id-1] if param + end end + attr_reader :id + + def initialize(worksheet, id, *args) + @worksheet = worksheet + @writer = Package::XMLWriterSimple.new + @id = id + + @row1, @row2, @col1, @col2, @param = handle_args(*args) + @columns = [] + @col_formats = [] + + # Set the data range rows (without the header and footer). + @first_data_row = @row1 + @first_data_row += 1 if ptrue?(@param[:header_row]) + @last_data_row = @row2 + @last_data_row -= 1 if @param[:total_row] + + set_the_table_options + set_the_table_style + set_the_table_name + set_the_table_and_autofilter_ranges + set_the_autofilter_range + + add_the_table_columns + write_the_cell_data_if_supplied + end + def set_xml_writer(filename) @writer.set_xml_writer(filename) end # @@ -39,65 +75,263 @@ # Close the XML writer object and filehandle. @writer.crlf @writer.close end - # - # Set the document properties. - # - def set_properties(properties) - @properties = properties + def add_the_table_columns + col_id = 0 + (@col1..@col2).each do |col_num| + # Set up the default column data. + col_data = Package::Table::ColumnData.new(col_id + 1, @param[:columns]) + + overrite_the_defaults_with_any_use_defined_values(col_id, col_data, col_num) + + # Store the column data. + @columns << col_data + + write_the_column_headers_to_the_worksheet(col_num, col_data) + + col_id += 1 + end # Table columns. end + def overrite_the_defaults_with_any_use_defined_values(col_id, col_data, col_num) + if @param[:columns] + # Check if there are user defined values for this column. + if user_data = @param[:columns][col_id] + # Map user defined values to internal values. + if user_data[:header] && !user_data[:header].empty? + col_data.name = user_data[:header] + end + # Handle the column formula. + handle_the_column_formula( + col_data, col_num, user_data[:formula], user_data[:format] + ) + + # Handle the function for the total row. + if user_data[:total_function] + handle_the_function_for_the_table_row( + @row2, col_data, col_num, + user_data[:total_function], + user_data[:format] + ) + elsif user_data[:total_string] + total_label_only( + @row2, col_num, col_data, user_data[:total_string], user_data[:format] + ) + end + + # Get the dxf format index. + if user_data[:format] + col_data.format = user_data[:format].get_dxf_index + end + + # Store the column format for writing the cell data. + # It doesn't matter if it is undefined. + @col_formats[col_id] = user_data[:format] + end + end + end + + def write_the_column_headers_to_the_worksheet(col_num, col_data) + if @param[:header_row] != 0 + @worksheet.write_string(@row1, col_num, col_data.name) + end + end + + def write_the_cell_data_if_supplied + return unless @param[:data] + + data = @param[:data] + i = 0 # For indexing the row data. + (@first_data_row..@last_data_row).each do |row| + next unless data[i] + + j = 0 # For indexing the col data. + (@col1..@col2).each do |col| + token = data[i][j] + @worksheet.write(row, col, token, @col_formats[j]) if token + j += 1 + end + i += 1 + end + end + private - # - # Write the XML declaration. - # - def write_xml_declaration - @writer.xml_decl('UTF-8', 1) + def handle_args(*args) + # Check for a cell reference in A1 notation and substitute row and column + row1, col1, row2, col2, param = row_col_notation(args) + + # Check for a valid number of args. + raise "Not enough parameters to add_table()" if [row1, col1, row2, col2].include?(nil) + + # Check that row and col are valid without storing the values. + check_dimensions_and_update_max_min_values(row1, col1, 1, 1) + check_dimensions_and_update_max_min_values(row2, col2, 1, 1) + + # Swap last row/col for first row/col as necessary. + row1, row2 = row2, row1 if row1 > row2 + col1, col2 = col2, col1 if col1 > col2 + + # The final hash contains the validation parameters. + param ||= {} + + # Turn on Excel's defaults. + param[:banded_rows] ||= 1 + param[:header_row] ||= 1 + param[:autofilter] ||= 1 + + # If the header row if off the default is to turn autofilter off. + param[:autofilter] = 0 if param[:header_row] == 0 + + check_parameter(param, valid_table_parameter, 'add_table') + + [row1, row2, col1, col2, param] end + # List of valid input parameters. + def valid_table_parameter + [ + :autofilter, + :banded_columns, + :banded_rows, + :columns, + :data, + :first_column, + :header_row, + :last_column, + :name, + :style, + :total_row + ] + end + + def handle_the_column_formula(col_data, col_num, formula, format) + return unless formula + + # Remove the leading = from formula. + formula.sub!(/^=/, '') + # Covert Excel 2010 "@" ref to 2007 "#This Row". + formula.gsub!(/@/,'[#This Row],') + + col_data.formula = formula + + (@first_data_row..@last_data_row).each do |row| + @worksheet.write_formula(row, col_num, formula, format) + end + end + + def handle_the_function_for_the_table_row(row2, col_data, col_num, total_function, format) + function = total_function + + # Massage the function name. + function = function.downcase + function.gsub!(/_/, '') + function.gsub!(/\s/,'') + + function = 'countNums' if function == 'countnums' + function = 'stdDev' if function == 'stddev' + + col_data.total_function = function + + formula = table_function_to_formula(function, col_data.name) + @worksheet.write_formula(row2, col_num, formula, format) + end + # - # Write the <autoFilter> element. + # Convert a table total function to a worksheet formula. # - def write_auto_filter - autofilter = @properties[:_autofilter] + def table_function_to_formula(function, col_name) + subtotals = { + :average => 101, + :countNums => 102, + :count => 103, + :max => 104, + :min => 105, + :stdDev => 107, + :sum => 109, + :var => 110 + } - return unless autofilter + unless func_num = subtotals[function.to_sym] + raise "Unsupported function '#{function}' in add_table()" + end + "SUBTOTAL(#{func_num},[#{col_name}])" + end - attributes = ['ref', autofilter] + # Total label only (not a function). + def total_label_only(row2, col_num, col_data, total_string, format) + col_data.total_string = total_string - @writer.empty_tag('autoFilter', attributes) + @worksheet.write_string(row2, col_num, total_string, format) end + def set_the_table_options + @show_first_col = ptrue?(@param[:first_column]) ? 1 : 0 + @show_last_col = ptrue?(@param[:last_column]) ? 1 : 0 + @show_row_stripes = ptrue?(@param[:banded_rows]) ? 1 : 0 + @show_col_stripes = ptrue?(@param[:banded_columns]) ? 1 : 0 + @header_row_count = ptrue?(@param[:header_row]) ? 1 : 0 + @totals_row_shown = ptrue?(@param[:total_row]) ? 1 : 0 + end + + def set_the_table_style + if @param[:style] + @style = @param[:style] + # Remove whitespace from style name. + @style.gsub!(/\s/, '') + else + @style = "TableStyleMedium9" + end + end + + def set_the_table_name + if @param[:name] + @name = @param[:name] + else + # Set a default name. + @name = "Table#{id}" + end + end + + def set_the_table_and_autofilter_ranges + @range = xl_range(@row1, @row2, @col1, @col2) + @a_range = xl_range(@row1, @last_data_row, @col1, @col2) + end + + def set_the_autofilter_range + @autofilter = @a_range if ptrue?(@param[:autofilter]) + end + # + # Write the XML declaration. + # + def write_xml_declaration + @writer.xml_decl('UTF-8', 1) + end + + # # Write the <table> element. # def write_table schema = 'http://schemas.openxmlformats.org/' xmlns = "#{schema}spreadsheetml/2006/main" - id = @properties[:id] - name = @properties[:_name] - display_name = @properties[:_name] - ref = @properties[:_range] - totals_row_shown = @properties[:_totals_row_shown] - header_row_count = @properties[:_header_row_count] attributes = [ 'xmlns', xmlns, 'id', id, - 'name', name, - 'displayName', display_name, - 'ref', ref + 'name', @name, + 'displayName', @name, + 'ref', @range ] - unless ptrue?(header_row_count) + unless ptrue?(@header_row_count) attributes << 'headerRowCount' << 0 end - if ptrue?(totals_row_shown) + if ptrue?(@totals_row_shown) attributes << 'totalsRowCount' << 1 else attributes << 'totalsRowShown' << 0 end @writer.start_tag('table', attributes) @@ -105,80 +339,68 @@ # # Write the <autoFilter> element. # def write_auto_filter - autofilter = @properties[:_autofilter] + return unless ptrue?(@autofilter) - return unless ptrue?(autofilter) + attributes = ['ref', @autofilter] - attributes = ['ref', autofilter] - @writer.empty_tag('autoFilter', attributes) end # # Write the <tableColumns> element. # def write_table_columns - columns = @properties[:_columns] + count = @columns.size - count = columns.size - attributes = ['count', count] @writer.tag_elements('tableColumns', attributes) do - columns.each {|col_data| write_table_column(col_data)} + @columns.each {|col_data| write_table_column(col_data)} end end # # Write the <tableColumn> element. # def write_table_column(col_data) attributes = [ - 'id', col_data[:_id], - 'name', col_data[:_name] + 'id', col_data.id, + 'name', col_data.name ] - if ptrue?(col_data[:_total_string]) - attributes << :totalsRowLabel << col_data[:_total_string] - elsif ptrue?(col_data[:_total_function]) - attributes << :totalsRowFunction << col_data[:_total_function] + if ptrue?(col_data.total_string) + attributes << :totalsRowLabel << col_data.total_string + elsif ptrue?(col_data.total_function) + attributes << :totalsRowFunction << col_data.total_function end - if col_data[:_format] - attributes << :dataDxfId << col_data[:_format] + if col_data.format + attributes << :dataDxfId << col_data.format end - if ptrue?(col_data[:_formula]) + if ptrue?(col_data.formula) @writer.tag_elements('tableColumn', attributes) do # Write the calculatedColumnFormula element. - write_calculated_column_formula(col_data[:_formula]) + write_calculated_column_formula(col_data.formula) end else @writer.empty_tag('tableColumn', attributes) end end # # Write the <tableStyleInfo> element. # def write_table_style_info - props = @properties - - name = props[:_style] - show_first_column = props[:_show_first_col] - show_last_column = props[:_show_last_col] - show_row_stripes = props[:_show_row_stripes] - show_column_stripes = props[:_show_col_stripes] - attributes = [ - 'name', name, - 'showFirstColumn', show_first_column, - 'showLastColumn', show_last_column, - 'showRowStripes', show_row_stripes, - 'showColumnStripes', show_column_stripes + 'name', @style, + 'showFirstColumn', @show_first_col, + 'showLastColumn', @show_last_col, + 'showRowStripes', @show_row_stripes, + 'showColumnStripes', @show_col_stripes ] @writer.empty_tag('tableStyleInfo', attributes) end