summaryrefslogtreecommitdiffhomepage
path: root/lib
diff options
context:
space:
mode:
authorWeston Ganger <[email protected]>2022-09-29 20:45:39 -0700
committerWeston Ganger <[email protected]>2022-09-29 20:45:39 -0700
commitfd8366970d9cb3f5fb431ba6c40a2a2ac2737615 (patch)
tree4e7fc2f0520d3a5cd4485d101b60a8280e624bb7 /lib
parentbcc88ca556b3b8527ba0ad56424f93ef170b9c31 (diff)
downloadcaxlsx-fd8366970d9cb3f5fb431ba6c40a2a2ac2737615.tar.gz
caxlsx-fd8366970d9cb3f5fb431ba6c40a2a2ac2737615.zip
Merge axlsx_styler gem into caxlsx
Diffstat (limited to 'lib')
-rw-r--r--lib/axlsx/package.rb8
-rw-r--r--lib/axlsx/stylesheet/styles.rb37
-rw-r--r--lib/axlsx/workbook/workbook.rb28
-rw-r--r--lib/axlsx/workbook/worksheet/border_creator.rb82
-rw-r--r--lib/axlsx/workbook/worksheet/cell.rb31
-rw-r--r--lib/axlsx/workbook/worksheet/worksheet.rb38
-rw-r--r--lib/caxlsx.rb10
7 files changed, 234 insertions, 0 deletions
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..3d557a1a 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,7 +223,40 @@ 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={})
+ ### TODO: Refractor this
+
+ if options[:type] == :dxf
+ style_id = original_add_style(options)
+ else
+ # Add styles to style_index cache for re-use
+
+ 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
+
+ style_id = style_index.key(raw_style)
+
+ if !style_id
+ style_id = original_add_style(options)
+
+ style_index[style_id] = raw_style
+ end
+ end
+
+ return style_id
+ end
+
+ def original_add_style(options={})
# Default to :xf
options[:type] ||= :xf
raise ArgumentError, "Type must be one of [:xf, :dxf]" unless [:xf, :dxf].include?(options[:type] )
diff --git a/lib/axlsx/workbook/workbook.rb b/lib/axlsx/workbook/workbook.rb
index edf719d1..f9e857fc 100644
--- a/lib/axlsx/workbook/workbook.rb
+++ b/lib/axlsx/workbook/workbook.rb
@@ -187,6 +187,34 @@ require 'axlsx/workbook/worksheet/selection.rb'
@styles
end
+ # An array that holds all cells with styles
+ attr_accessor :styled_cells
+
+ # Checks if styles are indexed to make it work for pre 0.1.5 version
+ # users that still explicitly call @workbook.apply_styles
+ attr_accessor :styles_applied
+
+ # A helper to apply styles that were added using `worksheet.add_style`
+ # @return [Boolean]
+ require 'active_support/core_ext/hash/deep_merge' ### TODO: keep or replace with local solution
+ 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 = current_style.deep_merge(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..a38e78b3
--- /dev/null
+++ b/lib/axlsx/workbook/worksheet/border_creator.rb
@@ -0,0 +1,82 @@
+# 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] || :all
+ @width = args[:style] || :thin
+ @color = args[:color] || '000000'
+ else
+ @edges = args || :all
+ @width = :thin
+ @color = '000000'
+ end
+ end
+
+ def draw
+ selected_edges(edges).each { |edge| add_border(edge, width, color) }
+ end
+
+ private
+
+ def selected_edges(edges)
+ all_edges = [:top, :right, :bottom, :left]
+ if edges == :all
+ all_edges
+ elsif edges.is_a?(Array) && edges - all_edges == []
+ edges.uniq
+ else
+ []
+ end
+ end
+
+ def add_border(position, width, color)
+ style = {
+ border: {
+ style: width, color: color, edges: [position.to_sym]
+ }
+ }
+ worksheet.add_style border_cells[position.to_sym], style
+ end
+
+ def border_cells
+ # example range "B2:D5"
+ {
+ top: "#{first_cell}:#{last_col}#{first_row}", # "B2:D2"
+ right: "#{last_col}#{first_row}:#{last_cell}", # "D2:D5"
+ bottom: "#{first_col}#{last_row}:#{last_cell}", # "B5:D5"
+ left: "#{first_cell}:#{first_col}#{last_row}" # "B2:B5"
+ }
+ 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..626cf60f 100644
--- a/lib/axlsx/workbook/worksheet/cell.rb
+++ b/lib/axlsx/workbook/worksheet/cell.rb
@@ -82,6 +82,37 @@ module Axlsx
defined?(@style) ? @style : 0
end
+ attr_accessor :raw_style
+
+ require 'active_support/core_ext/hash/deep_merge' ### TODO: can/should we remove this dependency
+ # The index of the cellXfs item to be applied to this cell.
+ # @param [Hash] styles
+ # @see Axlsx::Styles
+ require 'set' ### TODO: move to appropriate place
+ def add_style(style)
+ self.raw_style ||= {}
+
+ # using deep_merge from active_support:
+ # with regular Hash#merge adding borders fails miserably
+ new_style = raw_style.deep_merge(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 ||= Set.new
+ 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..527d2e71 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 styles TODO: how to specify this
+ 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|Symbol] options TODO: describe this
+ def add_border(cell_refs, options = :all) ### TODO: will we support the :all argument
+ 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
diff --git a/lib/caxlsx.rb b/lib/caxlsx.rb
index a82d49fb..1dcc6005 100644
--- a/lib/caxlsx.rb
+++ b/lib/caxlsx.rb
@@ -1,2 +1,12 @@
# encoding: UTF-8
require 'axlsx.rb'
+
+begin
+ require "axlsx_styler"
+
+ if defined?(AxlsxStyler)
+ raise StandardError.new("Please remove `axlsx_styler` from your Gemfile, the associated functionality is now built-in to `caxlsx` directly.")
+ end
+rescue LoadError
+ # Do nothing, all good
+end