lib/axlsx/stylesheet/styles.rb in axlsx-1.1.1 vs lib/axlsx/stylesheet/styles.rb in axlsx-1.1.2

- old
+ new

@@ -12,10 +12,11 @@ require 'axlsx/stylesheet/num_fmt.rb' require 'axlsx/stylesheet/pattern_fill.rb' require 'axlsx/stylesheet/table_style.rb' require 'axlsx/stylesheet/table_styles.rb' require 'axlsx/stylesheet/table_style_element.rb' + require 'axlsx/stylesheet/dxf.rb' require 'axlsx/stylesheet/xf.rb' require 'axlsx/stylesheet/cell_protection.rb' #The Styles class manages worksheet styles # In addition to creating the require style objects for a valid xlsx package, this class provides the key mechanism for adding styles to your workbook, and safely applying them to the cells of your worksheet. @@ -135,10 +136,11 @@ # @option options [String] format_code The formatting to apply. If this is specified, num_fmt is ignored. # @option options [Integer|Hash] border The border style to use. # @option options [String] bg_color The background color to apply to the cell # @option options [Boolean] hidden Indicates if the cell should be hidden # @option options [Boolean] locked Indicates if the cell should be locked + # @option options [Symbol] type What type of style is this. Options are [:dxf, :xf]. :xf is default # @option options [Hash] alignment A hash defining any of the attributes used in CellAlignment # @see CellAlignment # # @example You Got Style # require "rubygems" # if that is your preferred way to manage gems! @@ -187,71 +189,160 @@ # ws.add_row :values => ["Q2", 3000, 30], :style=>[title, currency, percent] # ws.add_row :values => ["Q3", 1000, 10], :style=>[title, currency, percent] # ws.add_row :values => ["Q4", 2000, 20], :style=>[title, currency, percent] # f = File.open('example_you_got_style.xlsx', 'w') # p.serialize(f) + # + # @example Differential styling + # # Differential styles apply on top of cell styles. Used in Conditional Formatting. Must specify :type => :dxf, and you can't use :num_fmt. + # require "rubygems" # if that is your preferred way to manage gems! + # require "axlsx" + # + # p = Axlsx::Package.new + # wb = p.workbook + # ws = wb.add_worksheet + # + # # define your styles + # profitable = wb.styles.add_style(:bg_color => "FFFF0000", + # :fg_color=>"#FF000000", + # :type => :dxf) + # + # ws.add_row :values => ["Genreated At:", Time.now], :styles=>[nil, date_time] + # ws.add_row :values => ["Previous Year Quarterly Profits (JPY)"], :style=>title + # ws.add_row :values => ["Quarter", "Profit", "% of Total"], :style=>title + # ws.add_row :values => ["Q1", 4000, 40], :style=>[title, currency, percent] + # ws.add_row :values => ["Q2", 3000, 30], :style=>[title, currency, percent] + # ws.add_row :values => ["Q3", 1000, 10], :style=>[title, currency, percent] + # ws.add_row :values => ["Q4", 2000, 20], :style=>[title, currency, percent] + # + # ws.add_conditional_formatting("A1:A7", { :type => :cellIs, :operator => :greaterThan, :formula => "2000", :dxfId => profitable, :priority => 1 }) + # f = File.open('example_differential_styling', 'w') + # p.serialize(f) + # def add_style(options={}) + # Default to :xf + options[:type] ||= :xf + raise ArgumentError, "Type must be one of [:xf, :dxf]" unless [:xf, :dxf].include?(options[:type] ) - numFmtId = if options[:format_code] - n = @numFmts.map{ |f| f.numFmtId }.max + 1 - numFmts << NumFmt.new(:numFmtId => n, :formatCode=> options[:format_code]) - n - else - options[:num_fmt] || 0 - end + fill = parse_fill_options options + font = parse_font_options options + numFmt = parse_num_fmt_options options + border = parse_border_options options + alignment = parse_alignment_options options + protection = parse_protection_options options - borderId = options[:border] || 0 - - if borderId.is_a?(Hash) - raise ArgumentError, "border hash definitions must include both style and color" unless borderId.keys.include?(:style) && borderId.keys.include?(:color) - - s = borderId[:style] - c = borderId[:color] - edges = borderId[:edges] || [:left, :right, :top, :bottom] - - border = Border.new - edges.each {|pr| border.prs << BorderPr.new(:name => pr, :style=>s, :color => Color.new(:rgb => c))} - borderId = self.borders << border + case options[:type] + when :dxf + style = Dxf.new :fill => fill, :font => font, :numFmt => numFmt, :border => border, :alignment => alignment, :protection => protection + else + style = Xf.new :fillId=>fill || 0, :fontId=>font || 0, :numFmtId=>numFmt || 0, :borderId=>border || 0, :alignment => alignment, :protection => protection, :applyFill=>!fill.nil?, :applyFont=>!font.nil?, :applyNumberFormat =>!numFmt.nil?, :applyBorder=>!border.nil?, :applyAlignment => !alignment.nil?, :applyProtection => !protection.nil? end - raise ArgumentError, "Invalid borderId" unless borderId < borders.size + options[:type] == :xf ? cellXfs << style : dxfs << style + end - fill = if options[:bg_color] - color = Color.new(:rgb=>options[:bg_color]) - pattern = PatternFill.new(:patternType =>:solid, :fgColor=>color) - fills << Fill.new(pattern) - else - 0 - end + # parses add_style options for protection styles + # noop if options hash does not include :hide or :locked key - fontId = if (options.values_at(:fg_color, :sz, :b, :i, :u, :strike, :outline, :shadow, :charset, :family, :font_name).length) - font = Font.new() - [:b, :i, :u, :strike, :outline, :shadow, :charset, :family, :sz].each { |k| font.send("#{k}=", options[k]) unless options[k].nil? } - font.color = Color.new(:rgb => options[:fg_color]) unless options[:fg_color].nil? - font.name = options[:font_name] unless options[:font_name].nil? - fonts << font - else - 0 - end + # @option options [Boolean] hide boolean value defining cell protection attribute for hiding. + # @option options [Boolean] locked boolean value defining cell protection attribute for locking. + # @return [CellProtection] + def parse_protection_options(options={}) + return if (options.keys & [:hidden, :locked]).empty? + CellProtection.new(options) + end - applyProtection = (options[:hidden] || options[:locked]) ? 1 : 0 + # parses add_style options for alignment + # noop if options hash does not include :alignment key + # @option options [Hash] alignment A hash of options to prive the CellAlignment intializer + # @return [CellAlignment] + # @see CellAlignment + def parse_alignment_options(options={}) + return unless options[:alignment] + CellAlignment.new options[:alignment] + end - xf = Xf.new(:fillId => fill, :fontId=>fontId, :applyFill=>1, :applyFont=>1, :numFmtId=>numFmtId, :borderId=>borderId, :applyProtection=>applyProtection) + # parses add_style options for fonts. If the options hash contains :type => :dxf we return a new Font object. + # if not, we return the index of the newly created font object in the styles.fonts collection. + # @note noop if none of the options described here are set on the options parameter. + # @option options [Symbol] type The type of style object we are working with (dxf or xf) + # @option options [String] fg_color The text color + # @option options [Integer] sz The text size + # @option options [Boolean] b Indicates if the text should be bold + # @option options [Boolean] i Indicates if the text should be italicised + # @option options [Boolean] u Indicates if the text should be underlined + # @option options [Boolean] strike Indicates if the text should be rendered with a strikethrough + # @option options [Boolean] outline Indicates if the text should be rendered with a shadow + # @option options [Integer] charset The character set to use. + # @option options [Integer] family The font family to use. + # @option options [String] font_name The name of the font to use + # @return [Font|Integer] + def parse_font_options(options={}) + return if (options.keys & [:fg_color, :sz, :b, :i, :u, :strike, :outline, :shadow, :charset, :family, :font_name]).empty? + font = Font.new(options) + font.color = Color.new(:rgb => options[:fg_color]) if options[:fg_color] + font.name = options[:font_name] if options[:font_name] + options[:type] == :dxf ? font : fonts << font + end - xf.applyNumberFormat = true if xf.numFmtId > 0 - xf.applyBorder = true if borderId > 0 + # parses add_style options for fills. If the options hash contains :type => :dxf we return a Fill object. If not, we return the index of the fill after being added to the fills collection. + # @note noop if :bg_color is not specified in options + # @option options [String] bg_color The rgb color to apply to the fill + # @return [Fill|Integer] + def parse_fill_options(options={}) + return unless options[:bg_color] + color = Color.new(:rgb=>options[:bg_color]) + pattern = PatternFill.new(:patternType =>:solid, :fgColor=>color) + fill = Fill.new(pattern) + options[:type] == :dxf ? fill : fills << fill + end - if options[:alignment] - xf.alignment = CellAlignment.new(options[:alignment]) - xf.applyAlignment = true + # parses Style#add_style options for borders. + # @note noop if :border is not specified in options + # @option options [Hash|Integer] A border style definition hash or the index of an existing border. Border style definition hashes must include :style and color: key-value entries and may include an :edges entry that references an array of symbols identifying which border edges you wish to apply the style or any other valid Border initializer options. If the :edges entity is not provided the style is applied to all edges of cells that reference this style. + # @example + # #apply a thick red border to the top and bottom + # { :border => { :style => :thick, :color => "FFFF0000", :edges => [:top, :bottom] } + # @return [Border|Integer] + def parse_border_options(options={}) + return unless options[:border] + b_opts = options[:border] + if b_opts.is_a?(Hash) + raise ArgumentError, (ERR_INVALID_BORDER_OPTIONS % b_opts) unless b_opts.values_at(:style, :color).size == 2 + border = Border.new b_opts + (b_opts[:edges] || [:left, :right, :top, :bottom]).each do |edge| + b_options = { :name => edge, :style => b_opts[:style], :color => Color.new(:rgb => b_opts[:color]) } + border.prs << BorderPr.new(b_options) + end + options[:type] == :dxf ? border : borders << border + elsif b_opts.is_a? Integer + raise ArgumentError, (ERR_INVALID_BORDER_ID % b_opts) unless b_opts < borders.size + if options[:type] == :dxf + borders[b_opts].clone + else + border = b_opts + end end + end - if applyProtection - xf.protection = CellProtection.new(options) - end + # Parses Style#add_style options for number formatting. + # noop if neither :format_code or :num_format options are set. + # @option options [Hash] A hash describing the :format_code and/or :num_fmt integer for the style. + # @return [NumFmt|Integer] + def parse_num_fmt_options(options={}) + return if (options.keys & [:format_code, :num_fmt]).empty? - cellXfs << xf + #When the user provides format_code - we always need to create a new numFmt object + #When the type is :dxf we always need to create a new numFmt object + if options[:format_code] || options[:type] == :dxf + #If this is a standard xf we pull from numFmts the highest current and increment for num_fmt + options[:num_fmt] ||= (@numFmts.map{ |num_fmt| num_fmt.numFmtId }.max + 1) if options[:type] != :dxf + numFmt = NumFmt.new(:numFmtId => options[:num_fmt] || 0, :formatCode=> options[:format_code].to_s) + options[:type] == :dxf ? numFmt : (numFmts << numFmt; numFmt.numFmtId) + else + options[:num_fmt] + end end # Serializes the object # @param [String] str # @return [String] @@ -304,10 +395,10 @@ @cellXfs << Xf.new(:borderId=>1, :xfId=>0, :numFmtId=>0, :fontId=>0, :fillId=>0) # default date formatting @cellXfs << Xf.new(:borderId=>0, :xfId=>0, :numFmtId=>14, :fontId=>0, :fillId=>0, :applyNumberFormat=>1) @cellXfs.lock - @dxfs = SimpleTypedList.new(Xf, "dxfs"); @dxfs.lock + @dxfs = SimpleTypedList.new(Dxf, "dxfs"); @dxfs.lock @tableStyles = TableStyles.new(:defaultTableStyle => "TableStyleMedium9", :defaultPivotStyle => "PivotStyleLight16"); @tableStyles.lock end end end