# -*- coding: utf-8 -*- require 'write_xlsx/package/xml_writer_simple' require 'write_xlsx/utility' module Writexlsx module Package class Styles include Writexlsx::Utility def initialize @writer = Package::XMLWriterSimple.new @xf_formats = nil @palette = [] @font_count = 0 @num_format_count = 0 @border_count = 0 @fill_count = 0 @custom_colors = [] @dxf_formats = [] end def set_xml_writer(filename) @writer.set_xml_writer(filename) end def assemble_xml_file write_xml_declaration do write_style_sheet { write_style_sheet_base } end end # # Pass in the Format objects and other properties used to set the styles. # def set_style_properties(xf_formats, palette, font_count, num_format_count, border_count, fill_count, custom_colors, dxf_formats) @xf_formats = xf_formats @palette = palette @font_count = font_count @num_format_count = num_format_count @border_count = border_count @fill_count = fill_count @custom_colors = custom_colors @dxf_formats = dxf_formats end # # Convert from an Excel internal colour index to a XML style #RRGGBB index # based on the default or user defined values in the Workbook palette. # def palette_color(index) if index.to_s =~ /^#([0-9A-F]{6})$/i "FF#{$1.upcase}" else "FF#{super(index)}" end end # # Write the element. # def write_style_sheet attributes = [ ['xmlns', XMLWriterSimple::XMLNS] ] @writer.tag_elements('styleSheet', attributes) { yield } end # # Write the element. # def write_num_fmts count = @num_format_count return if count == 0 attributes = [ ['count', count] ] @writer.tag_elements('numFmts', attributes) do # Write the numFmts elements. @xf_formats.each do |format| # Ignore built-in number formats, i.e., < 164. next unless format.num_format_index >= 164 write_num_fmt(format.num_format_index, format.num_format) end end end FORMAT_CODES = { 0 => 'General', 1 => '0', 2 => '0.00', 3 => '#,##0', 4 => '#,##0.00', 5 => '($#,##0_);($#,##0)', 6 => '($#,##0_);[Red]($#,##0)', 7 => '($#,##0.00_);($#,##0.00)', 8 => '($#,##0.00_);[Red]($#,##0.00)', 9 => '0%', 10 => '0.00%', 11 => '0.00E+00', 12 => '# ?/?', 13 => '# ??/??', 14 => 'm/d/yy', 15 => 'd-mmm-yy', 16 => 'd-mmm', 17 => 'mmm-yy', 18 => 'h:mm AM/PM', 19 => 'h:mm:ss AM/PM', 20 => 'h:mm', 21 => 'h:mm:ss', 22 => 'm/d/yy h:mm', 37 => '(#,##0_);(#,##0)', 38 => '(#,##0_);[Red](#,##0)', 39 => '(#,##0.00_);(#,##0.00)', 40 => '(#,##0.00_);[Red](#,##0.00)', 41 => '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)', 42 => '_($* #,##0_);_($* (#,##0);_($* "-"_);_(@_)', 43 => '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)', 44 => '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)', 45 => 'mm:ss', 46 => '[h]:mm:ss', 47 => 'mm:ss.0', 48 => '##0.0E+0', 49 => '@' } # # Write the element. # def write_num_fmt(num_fmt_id, format_code) # Set the format code for built-in number formats. format_code = FORMAT_CODES[num_fmt_id] || 'General' if num_fmt_id < 164 attributes = [ ['numFmtId', num_fmt_id], ['formatCode', format_code] ] @writer.empty_tag('numFmt', attributes) end # # Write the element. # def write_fonts write_format_elements('fonts', @font_count) do write_font_base end end def write_font_base @xf_formats.each do |format| format.write_font(@writer, self) if format.has_font? end end # # Write the element. # def write_color(name, value) attributes = [ [name, value] ] @writer.empty_tag('color', attributes) end # # Write the element. # def write_fills attributes = [ ['count', @fill_count] ] @writer.tag_elements('fills', attributes) do write_fills_base end end def write_fills_base # Write the default fill element. write_default_fill('none') write_default_fill('gray125') # Write the fill elements for format objects that have them. @xf_formats.each do |format| write_fill(format) if format.has_fill? end end # # Write the element for the default fills. # def write_default_fill(pattern_type) @writer.tag_elements('fill') do @writer.empty_tag('patternFill', [ ['patternType', pattern_type] ]) end end PATTERNS = %w( none solid mediumGray darkGray lightGray darkHorizontal darkVertical darkDown darkUp darkGrid darkTrellis lightHorizontal lightVertical lightDown lightUp lightGrid lightTrellis gray125 gray0625 ) # # Write the element. # def write_fill(format, dxf_format = nil) @writer.tag_elements('fill' ) do write_fill_base(format, dxf_format) end end def write_fill_base(format, dxf_format) # The "none" pattern is handled differently for dxf formats. if dxf_format && format.pattern <= 1 attributes = [] else attributes = [ ['patternType', PATTERNS[format.pattern]] ] end @writer.tag_elements('patternFill', attributes) do write_pattern_fill(format, dxf_format) end end def write_pattern_fill(format, dxf_format) bg_color, fg_color = bg_and_fg_color(format, dxf_format) if fg_color && fg_color != 0 @writer.empty_tag('fgColor', [ ['rgb', palette_color(fg_color)] ]) end if bg_color && bg_color != 0 @writer.empty_tag('bgColor', [ ['rgb', palette_color(bg_color)] ]) else @writer.empty_tag('bgColor', [ ['indexed', 64] ]) if !dxf_format end end def bg_and_fg_color(format, dxf_format) bg_color = format.bg_color fg_color = format.fg_color # Colors for dxf formats are handled differently from normal formats since # the normal format reverses the meaning of BG and FG for solid fills. if dxf_format && dxf_format != 0 bg_color = format.dxf_bg_color fg_color = format.dxf_fg_color end [bg_color, fg_color] end # # Write the element. # def write_borders write_format_elements('borders', @border_count) do write_borders_base end end def write_borders_base @xf_formats.each do |format| write_border(format) if format.has_border? end end def write_format_elements(elements, count) attributes = [ [ 'count', count] ] @writer.tag_elements(elements, attributes) do # Write the border elements for format objects that have them. yield end end # # Write the element. # def write_border(format, dxf_format = nil) # Write the start border tag. @writer.tag_elements('border', format.border_attributes) do write_border_base(format, dxf_format) end end def write_border_base(format, dxf_format) # Write the sub elements. write_border_sub_elements(format) # Condition DXF formats don't allow diagonal borders if dxf_format write_sub_border('vertical') write_sub_border('horizontal') else # Ensure that a default diag border is set if the diag type is set. format.diag_border = 1 if format.diag_type != 0 && format.diag_border == 0 write_sub_border('diagonal', format.diag_border, format.diag_color) end end def write_border_sub_elements(format) write_sub_border('left', format.left, format.left_color) write_sub_border('right', format.right, format.right_color) write_sub_border('top', format.top, format.top_color) write_sub_border('bottom', format.bottom, format.bottom_color) end BORDER_STYLES = %w( none thin medium dashed dotted thick double hair mediumDashed dashDot mediumDashDot dashDotDot mediumDashDotDot slantDashDot ) # # Write the sub elements such as , , etc. # def write_sub_border(type, style = 0, color = nil) if style == 0 @writer.empty_tag(type) return end attributes = [ [:style, BORDER_STYLES[style]] ] @writer.tag_elements(type, attributes) do if color != 0 color = palette_color(color) @writer.empty_tag('color', [ ['rgb', color] ]) else @writer.empty_tag('color', [ ['auto', 1] ]) end end end # # Write the element. # def write_cell_style_xfs attributes = [ ['count', 1] ] @writer.tag_elements('cellStyleXfs', attributes) do # Write the style_xf element. write_style_xf end end # # Write the element. # def write_cell_xfs formats = @xf_formats # Workaround for when the last format is used for the comment font # and shouldn't be used for cellXfs. last_format = formats[-1] formats.pop if last_format && last_format.font_only != 0 attributes = [ ['count', formats.size] ] @writer.tag_elements('cellXfs', attributes) do # Write the xf elements. formats.each { |format| write_xf(format) } end end # # Write the style element. # def write_style_xf attributes = [ ['numFmtId', 0], ['fontId', 0], ['fillId', 0], ['borderId', 0] ] @writer.empty_tag('xf', attributes) end private def write_style_sheet_base write_num_fmts write_fonts write_fills write_borders write_cell_style_xfs write_cell_xfs write_cell_styles write_dxfs write_table_styles write_colors end # # Write the element. # def write_xf(format) # Check if XF format has alignment properties set. apply_align, align = format.get_align_properties # Check for cell protection properties. protection = format.get_protection_properties # Check if an alignment sub-element should be written. has_align = apply_align && !align.empty? # Write XF with sub-elements if required. if has_align || protection @writer.tag_elements('xf', format.xf_attributes) do @writer.empty_tag('alignment', align) if has_align @writer.empty_tag('protection', protection) if protection end else @writer.empty_tag('xf', format.xf_attributes) end end # # Write the element. # def write_cell_styles attributes = [ ['count', 1] ] @writer.tag_elements('cellStyles', attributes) do # Write the cellStyle element. write_cell_style end end # # Write the element. # def write_cell_style attributes = [ ['name', 'Normal'], ['xfId', 0], ['builtinId', 0] ] @writer.empty_tag('cellStyle', attributes) end # # Write the element. # def write_dxfs attributes = [ ['count', @dxf_formats.count] ] if @dxf_formats.empty? @writer.empty_tag('dxfs', attributes) else @writer.tag_elements('dxfs', attributes) do # Write the font elements for format objects that have them. @dxf_formats.each do |format| write_dxf(format) end end end end def write_dxf(format) @writer.tag_elements('dxf') do format.write_font(@writer, self, 1) if format.has_dxf_font? if format.num_format_index != 0 write_num_fmt(format.num_format_index, format.num_format) end write_fill(format, 1) if format.has_dxf_fill? write_border(format, 1) if format.has_dxf_border? end end # # Write the element. # def write_table_styles attributes = [ ['count', 0], ['defaultTableStyle', 'TableStyleMedium9'], ['defaultPivotStyle', 'PivotStyleLight16'] ] @writer.empty_tag('tableStyles', attributes) end # # Write the element. # def write_colors return if @custom_colors.empty? @writer.tag_elements( 'colors' ) do write_mru_colors(@custom_colors) end end # # Write the element for the most recently used colours. # def write_mru_colors(custom_colors) # Limit the mruColors to the last 10. count = custom_colors.size # array[-10, 10] returns array which contains last 10 items. custom_colors = custom_colors[-10, 10] if count > 10 @writer.tag_elements('mruColors') do # Write the custom colors in reverse order. custom_colors.reverse.each do |color| write_color('rgb', color) end end end # # Write the element. # def write_condense attributes = [ ['val', 0] ] @writer.empty_tag('condense', attributes) end # # Write the element. # def write_extend attributes = [ ['val', 0] ] @writer.empty_tag('extend', attributes) end end end end