diff options
| author | Noel Peden <[email protected]> | 2022-10-21 04:19:01 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-10-21 04:19:01 -0700 |
| commit | 315c3f04224ba2f4b44fe2ce633315a6e79f62cb (patch) | |
| tree | d67454ea279e19f25b37b05fee0a43a80d19a158 /lib | |
| parent | bcc88ca556b3b8527ba0ad56424f93ef170b9c31 (diff) | |
| parent | 449056492f8c9fd44684dbf7bbe18f401fe5e6ad (diff) | |
| download | caxlsx-315c3f04224ba2f4b44fe2ce633315a6e79f62cb.tar.gz caxlsx-315c3f04224ba2f4b44fe2ce633315a6e79f62cb.zip | |
Merge pull request #168 from westonganger/merge_axlsx_styler
Merge axlsx_styler gem into caxlsx
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/axlsx.rb | 22 | ||||
| -rw-r--r-- | lib/axlsx/package.rb | 8 | ||||
| -rw-r--r-- | lib/axlsx/stylesheet/styles.rb | 42 | ||||
| -rw-r--r-- | lib/axlsx/workbook/workbook.rb | 30 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/border_creator.rb | 76 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/cell.rb | 26 | ||||
| -rw-r--r-- | lib/axlsx/workbook/worksheet/worksheet.rb | 38 |
7 files changed, 241 insertions, 1 deletions
diff --git a/lib/axlsx.rb b/lib/axlsx.rb index f2403893..23082b71 100644 --- a/lib/axlsx.rb +++ b/lib/axlsx.rb @@ -28,8 +28,17 @@ require 'zip' #core dependencies require 'bigdecimal' +require 'set' require 'time' +begin + if Gem.loaded_specs.has_key?("axlsx_styler") + raise StandardError.new("Please remove `axlsx_styler` from your Gemfile, the associated functionality is now built-in to `caxlsx` directly.") + end +rescue + # Do nothing +end + # xlsx generation with charts, images, automated column width, customizable styles # and full schema validation. Axlsx excels at helping you generate beautiful # Office Open XML Spreadsheet documents without having to understand the entire @@ -166,6 +175,19 @@ module Axlsx end end + # utility method for performing a deep merge on a Hash + # @param [Hash] Hash to merge into + # @param [Hash] Hash to be added + def self.hash_deep_merge(first_hash, second_hash) + first_hash.merge(second_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + Axlsx.hash_deep_merge(this_val, other_val) + else + other_val + end + end + end + # Instructs the serializer to not try to escape cell value input. # This will give you a huge speed bonus, but if you content has <, > or other xml character data # the workbook will be invalid and excel will complain. diff --git a/lib/axlsx/package.rb b/lib/axlsx/package.rb index ccabf799..6baaa173 100644 --- a/lib/axlsx/package.rb +++ b/lib/axlsx/package.rb @@ -101,6 +101,10 @@ module Axlsx # s = p.to_stream() # File.open('example_streamed.xlsx', 'wb') { |f| f.write(s.read) } def serialize(output, options = {}, secondary_options = nil) + if !workbook.styles_applied + workbook.apply_styles + end + confirm_valid, zip_command = parse_serialize_options(options, secondary_options) return false unless !confirm_valid || self.validate.empty? zip_provider = if zip_command @@ -122,6 +126,10 @@ module Axlsx # @param [Boolean] confirm_valid Validate the package prior to serialization. # @return [StringIO|Boolean] False if confirm_valid and validation errors exist. rewound string IO if not. def to_stream(confirm_valid=false) + if !workbook.styles_applied + workbook.apply_styles + end + return false unless !confirm_valid || self.validate.empty? Relationship.initialize_ids_cache zip = write_parts(Zip::OutputStream.new(StringIO.new.binmode, true)) diff --git a/lib/axlsx/stylesheet/styles.rb b/lib/axlsx/stylesheet/styles.rb index fd741ac5..1ddbb788 100644 --- a/lib/axlsx/stylesheet/styles.rb +++ b/lib/axlsx/stylesheet/styles.rb @@ -120,6 +120,10 @@ module Axlsx load_default_styles end + def style_index + @style_index ||= {} + end + # Drastically simplifies style creation and management. # @return [Integer] # @option options [String] fg_color The text color @@ -219,11 +223,36 @@ module Axlsx # f = File.open('example_differential_styling', 'wb') # p.serialize(f) # + # An index for cell styles where keys are styles codes as per Axlsx::Style and values are Cell#raw_style + # The reason for the backward key/value ordering is that style lookup must be most efficient, while `add_style` can be less efficient 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] ) + if options[:border].is_a?(Hash) && options[:border][:edges] == :all + options[:border][:edges] = Axlsx::Border::EDGES + end + + if options[:type] == :xf + # Check to see if style in cache already + + font_defaults = {name: @fonts.first.name, sz: @fonts.first.sz, family: @fonts.first.family} + + raw_style = {type: :xf}.merge(font_defaults).merge(options) + + if raw_style[:format_code] + raw_style.delete(:num_fmt) + end + + xf_index = style_index.key(raw_style) + + if xf_index + return xf_index + end + end + fill = parse_fill_options options font = parse_font_options options numFmt = parse_num_fmt_options options @@ -238,7 +267,18 @@ module Axlsx 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 - options[:type] == :xf ? cellXfs << style : dxfs << style + if options[:type] == :xf + xf_index = (cellXfs << style) + + # Add styles to style_index cache for re-use + style_index[xf_index] = raw_style + + return xf_index + else + dxf_index = (dxfs << style) + + return dxf_index + end end # parses add_style options for protection styles diff --git a/lib/axlsx/workbook/workbook.rb b/lib/axlsx/workbook/workbook.rb index edf719d1..938d4aee 100644 --- a/lib/axlsx/workbook/workbook.rb +++ b/lib/axlsx/workbook/workbook.rb @@ -187,6 +187,36 @@ require 'axlsx/workbook/worksheet/selection.rb' @styles end + # An array that holds all cells with styles + # @return Set + def styled_cells + @styled_cells ||= Set.new + end + + # Are the styles added with workbook.add_styles applied yet + # @return Boolean + attr_accessor :styles_applied + + # A helper to apply styles that were added using `worksheet.add_style` + # @return [Boolean] + def apply_styles + return false if !styled_cells + + styled_cells.each do |cell| + current_style = styles.style_index[cell.style] + + if current_style + new_style = Axlsx.hash_deep_merge(current_style, cell.raw_style) + else + new_style = cell.raw_style + end + + cell.style = styles.add_style(new_style) + end + + self.styles_applied = true + end + # Indicates if the epoc date for serialization should be 1904. If false, 1900 is used. @@date1904 = false diff --git a/lib/axlsx/workbook/worksheet/border_creator.rb b/lib/axlsx/workbook/worksheet/border_creator.rb new file mode 100644 index 00000000..a4146ee1 --- /dev/null +++ b/lib/axlsx/workbook/worksheet/border_creator.rb @@ -0,0 +1,76 @@ +# encoding: UTF-8 + +module Axlsx + class BorderCreator + attr_reader :worksheet, :cells, :edges, :width, :color + + def initialize(worksheet, cells, args) + @worksheet = worksheet + @cells = cells + if args.is_a?(Hash) + @edges = args[:edges] || Axlsx::Border::EDGES + @width = args[:style] || :thin + @color = args[:color] || '000000' + else + @edges = args || Axlsx::Border::Edges + @width = :thin + @color = '000000' + end + + if @edges == :all + @edges = Axlsx::Border::EDGES + elsif @edges.is_a?(Array) + @edges = (@edges.map(&:to_sym).uniq & Axlsx::Border::EDGES) + else + @edges = [] + end + end + + def draw + @edges.each do |edge| + worksheet.add_style( + border_cells[edge], + { + border: {style: @width, color: @color, edges: [edge]} + } + ) + end + end + + private + + def border_cells + { + top: "#{first_cell}:#{last_col}#{first_row}", + right: "#{last_col}#{first_row}:#{last_cell}", + bottom: "#{first_col}#{last_row}:#{last_cell}", + left: "#{first_cell}:#{first_col}#{last_row}", + } + end + + def first_cell + @first_cell ||= cells.first.r + end + + def last_cell + @last_cell ||= cells.last.r + end + + def first_row + @first_row ||= first_cell.scan(/\d+/).first + end + + def first_col + @first_col ||= first_cell.scan(/\D+/).first + end + + def last_row + @last_row ||= last_cell.scan(/\d+/).first + end + + def last_col + @last_col ||= last_cell.scan(/\D+/).first + end + + end +end diff --git a/lib/axlsx/workbook/worksheet/cell.rb b/lib/axlsx/workbook/worksheet/cell.rb index 3277b8c5..125ca051 100644 --- a/lib/axlsx/workbook/worksheet/cell.rb +++ b/lib/axlsx/workbook/worksheet/cell.rb @@ -82,6 +82,32 @@ module Axlsx defined?(@style) ? @style : 0 end + attr_accessor :raw_style + + # The index of the cellXfs item to be applied to this cell. + # @param [Hash] styles + # @see Axlsx::Styles + def add_style(style) + self.raw_style ||= {} + + new_style = Axlsx.hash_deep_merge(raw_style, style) + + all_edges = [:top, :right, :bottom, :left] + + if !raw_style[:border].nil? && !style[:border].nil? + border_at = (raw_style[:border][:edges] || all_edges) + (style[:border][:edges] || all_edges) + new_style[:border][:edges] = border_at.uniq.sort + elsif !style[:border].nil? + new_style[:border] = style[:border] + end + + self.raw_style = new_style + + wb = row.worksheet.workbook + + wb.styled_cells << self + end + # The row this cell belongs to. # @return [Row] attr_reader :row diff --git a/lib/axlsx/workbook/worksheet/worksheet.rb b/lib/axlsx/workbook/worksheet/worksheet.rb index 921687c8..a240e8e9 100644 --- a/lib/axlsx/workbook/worksheet/worksheet.rb +++ b/lib/axlsx/workbook/worksheet/worksheet.rb @@ -1,4 +1,7 @@ # encoding: UTF-8 + +require_relative "border_creator" + module Axlsx # The Worksheet class represents a worksheet in the workbook. @@ -560,6 +563,41 @@ module Axlsx cells.each { |cell| cell.style = style } end + # Set the style for cells in a specific column + # @param [String|Array] cell references + # @param [Hash] styles + def add_style(cell_refs, *styles) + if !cell_refs.is_a?(Array) + cell_refs = [cell_refs] + end + + cell_refs.each do |cell_ref| + item = self[cell_ref] + + cells = item.is_a?(Array) ? item : [item] + + cells.each do |cell| + styles.each do |style| + cell.add_style(style) + end + end + end + end + + # Set the style for cells in a specific column + # @param [String|Array] cell references + # @param [Hash|Array|Symbol] border options + def add_border(cell_refs, options = Axlsx::Border::EDGES) + if !cell_refs.is_a?(Array) + cell_refs = [cell_refs] + end + + cell_refs.each do |cell_ref| + cells = self[cell_ref] + Axlsx::BorderCreator.new(self, cells, options).draw + end + end + # Returns a sheet node serialization for this sheet in the workbook. def to_sheet_node_xml_string(str='') add_autofilter_defined_name_to_workbook |
