Skip to content

Commit

Permalink
reduced processing of 3000 rows from 3+ seconds to just under 2
Browse files Browse the repository at this point in the history
  • Loading branch information
randym committed Jan 10, 2013
1 parent f7eeb07 commit 58e8dd8
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 83 deletions.
7 changes: 6 additions & 1 deletion lib/axlsx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,10 @@ def self.camel(s="", all_caps = true)
s = s.capitalize if all_caps
s.gsub(/_(.)/){ $1.upcase }
end

def self.trust_input
@trust_input ||= false
end
def self.trust_input=(v)
@trust_input=v
end
end
13 changes: 7 additions & 6 deletions lib/axlsx/util/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,18 @@ class DataTypeValidator
# @raise [ArugumentError] Raised if the class of the value provided is not in the specified array of types or the block passed returns false
# @return [Boolean] true if validation succeeds.
# @see validate_boolean
def self.validate(name, types, v, other= lambda{|arg| true })
def self.validate(name, types, v, other=false)
types = [types] unless types.is_a? Array
valid_type = false
if other.is_a?(Proc)
raise ArgumentError, (ERR_TYPE % [v.inspect, name, types.inspect]) unless other.call(v)
end
if v.class == Class
types.each { |t| valid_type = true if v.ancestors.include?(t) }
types.each { |t| return if v.ancestors.include?(t) }
else
types.each { |t| valid_type = true if v.is_a?(t) }
types.each { |t| return if v.is_a?(t) }
end
raise ArgumentError, (ERR_TYPE % [v.inspect, name, types.inspect]) unless (other.call(v) && valid_type)
raise ArgumentError, (ERR_TYPE % [v.inspect, name, types.inspect])
end
true
end


Expand Down
2 changes: 1 addition & 1 deletion lib/axlsx/workbook/shared_strings_table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def resolve(cells)
cell.send :ssti=, index
else
cell.send :ssti=, @index
@shared_xml_string << '<si>' << cell.run_xml_string << '</si>'
@shared_xml_string << '<si>' << CellSerializer.run_xml_string(cell) << '</si>'
@unique_cells[cell_hash] = @index
@index += 1
end
Expand Down
1 change: 1 addition & 0 deletions lib/axlsx/workbook/workbook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Axlsx
require 'axlsx/workbook/worksheet/date_time_converter.rb'
require 'axlsx/workbook/worksheet/protected_range.rb'
require 'axlsx/workbook/worksheet/protected_ranges.rb'
require 'axlsx/workbook/worksheet/cell_serializer.rb'
require 'axlsx/workbook/worksheet/cell.rb'
require 'axlsx/workbook/worksheet/page_margins.rb'
require 'axlsx/workbook/worksheet/page_set_up_pr.rb'
Expand Down
81 changes: 7 additions & 74 deletions lib/axlsx/workbook/worksheet/cell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ class Cell
# @option options [Symbol] scheme must be one of :none, major, :minor
def initialize(row, value="", options={})
self.row=row
@value = @font_name = @charset = @family = @b = @i = @strike = @outline = @shadow = nil
@formula_value = @condense = @u = @vertAlign = @sz = @color = @scheme = @extend = @ssti = nil
@value = nil
#@value = @font_name = @charset = @family = @b = @i = @strike = @outline = @shadow = nil
#@formula_value = @condense = @u = @vertAlign = @sz = @color = @scheme = @extend = @ssti = nil
@styles = row.worksheet.workbook.styles
@row.cells << self
parse_options options
Expand Down Expand Up @@ -293,42 +294,13 @@ def merge(target)
self.row.worksheet.merge_cells "#{self.r}:#{range_end}" unless range_end.nil?
end

# builds an xml text run based on this cells attributes.
# @param [String] str The string instance this run will be concated to.
# @return [String]
def run_xml_string(str = '')
if is_text_run?
data = instance_values.reject{|key, value| value == nil || key == 'value' || key == 'type' }
keys = data.keys & INLINE_STYLES
str << "<r><rPr>"
keys.each do |key|
case key
when 'font_name'
str << "<rFont val='"<< @font_name << "'/>"
when 'color'
str << data[key].to_xml_string
else
str << "<" << key.to_s << " val='" << data[key].to_s << "'/>"
end
end
str << "</rPr>" << "<t>" << value.to_s << "</t></r>"
else
str << "<t>" << value.to_s << "</t>"
end
str
end

# Serializes the cell
# @param [Integer] r_index The row index for the cell
# @param [Integer] c_index The cell index in the row.
# @param [String] str The string index the cell content will be appended to. Defaults to empty string.
# @return [String] xml text for the cell
def to_xml_string(r_index, c_index, str = '')
str << '<c r="' << Axlsx::cell_r(c_index, r_index) << '" s="' << @style.to_s << '" '
return str << '/>' if @value.nil?
method = (@type.to_s << '_type_serialization').to_sym
self.send(method, str)
str << '</c>'
CellSerializer.to_xml_string r_index, c_index, self, str
end

def is_formula?
Expand Down Expand Up @@ -397,9 +369,9 @@ def cell_type_from_value(v)
:time
elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
:boolean
elsif v.to_s.match(/\A[+-]?\d+?\Z/) #numeric
elsif v.to_s =~ /\A[+-]?\d+?\Z/ #numeric
:integer
elsif v.to_s.match(/\A[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\Z/) #float
elsif v.to_s =~ /\A[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\Z/ #float
:float
else
:string
Expand Down Expand Up @@ -428,46 +400,7 @@ def cast_value(v)
@type = :string
# TODO find a better way to do this as it accounts for 30% of
# processing time in benchmarking...
::CGI.escapeHTML(v.to_s)
end
end

def date_type_serialization(str='')
value_serialization 'd', DateTimeConverter::date_to_serial(@value).to_s, str
end

def time_type_serialization(str='')
value_serialization 'd', DateTimeConverter::time_to_serial(@value).to_s, str
end

def boolean_type_serialization(str='')
value_serialization 'b', @value.to_s, str
end

def float_type_serialization(str='')
numeric_type_serialization str
end

def integer_type_serialization(str = '')
numeric_type_serialization str
end

def numeric_type_serialization(str = '')
value_serialization('n', @value.to_s, str)
end

def value_serialization(serialization_type, serialization_value, str = '')
str << 't="' << serialization_type << '"><v>' << serialization_value << '</v>'
end

def string_type_serialization(str='')
if is_formula?
str << 't="str">' << '<f>' << value.to_s.sub('=', '') << '</f>'
str << '<v>' << formula_value.to_s << '</v>' unless formula_value.nil?
elsif !@ssti.nil?
value_serialization 's', @ssti.to_s, str
else
str << 't="inlineStr">' << '<is>' << run_xml_string << '</is>'
Axlsx::trust_input ? v.to_s : ::CGI.escapeHTML(v.to_s)
end
end

Expand Down
94 changes: 94 additions & 0 deletions lib/axlsx/workbook/worksheet/cell_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
module Axlsx
class CellSerializer
class << self
def to_xml_string(row_index, column_index, cell, str='')
str << '<c r="' << Axlsx::cell_r(column_index, row_index) << '" s="' << cell.style.to_s << '" '
return str << '/>' if cell.value.nil?
method = (cell.type.to_s << '_type_serialization').to_sym
self.send(method, cell, str)
str << '</c>'
end


# builds an xml text run based on this cells attributes.
# @param [String] str The string instance this run will be concated to.
# @return [String]
def run_xml_string(cell, str = '')
if cell.is_text_run?
data = cell.instance_values.reject{|key, value| value == nil || key == 'value' || key == 'type' }
keys = data.keys & Cell::INLINE_STYLES
str << "<r><rPr>"
keys.each do |key|
case key
when 'font_name'
str << "<rFont val='"<< cell.font_name << "'/>"
when 'color'
str << data[key].to_xml_string
else
str << "<" << key.to_s << " val='" << data[key].to_s << "'/>"
end
end
str << "</rPr>" << "<t>" << cell.value.to_s << "</t></r>"
else
str << "<t>" << cell.value.to_s << "</t>"
end
str
end


def iso_8601_type_serialization(cell, str='')
value_serialization 'd', cell.value, str
end

def date_type_serialization(cell, str='')
value_serialization false, DateTimeConverter::date_to_serial(cell.value).to_s, str
end

def time_type_serialization(cell, str='')
value_serialization false, DateTimeConverter::time_to_serial(cell.value).to_s, str
end

def boolean_type_serialization(cell, str='')
value_serialization 'b', cell.value.to_s, str
end

def float_type_serialization(cell, str='')
numeric_type_serialization cell, str
end

def integer_type_serialization(cell, str = '')
numeric_type_serialization cell, str
end

def numeric_type_serialization(cell, str = '')
value_serialization 'n', cell.value.to_s, str
end

def value_serialization(serialization_type, serialization_value, str = '')
str << 't="' << serialization_type << '"' if serialization_type
str << '><v>' << serialization_value << '</v>'
end

def formula_serialization(cell, str='')
str << 't="str">' << '<f>' << cell.value.to_s.sub('=', '') << '</f>'
str << '<v>' << cell.formula_value.to_s << '</v>' unless cell.formula_value.nil?
end

def inline_string_serialization(cell, str = '')
str << 't="inlineStr">' << '<is>'
run_xml_string cell, str
str << '</is>'
end

def string_type_serialization(cell, str='')
if cell.is_formula?
formula_serialization cell, str
elsif !cell.ssti.nil?
value_serialization 's', cell.ssti.to_s, str
else
inline_string_serialization cell, str
end
end
end
end
end
4 changes: 3 additions & 1 deletion lib/axlsx/workbook/worksheet/col.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ def update_width(cell, fixed_width=nil, use_autowidth=true)
if fixed_width.is_a? Numeric
self.width = fixed_width
elsif use_autowidth
self.width = [width || 0, cell.autowidth || 0].max
cell_width = cell.autowidth
self.width = cell_width unless (width || 0) > (cell_width || 0)
#self.width = [width || 0, cell.autowidth || 0].max
end
end

Expand Down
1 change: 1 addition & 0 deletions test/benchmark.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'axlsx'
require 'csv'
require 'benchmark'
Axlsx::trust_input = true
row = []
input = (32..126).to_a.pack('U*').chars.to_a
20.times { row << input.shuffle.join}
Expand Down
1 change: 1 addition & 0 deletions test/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
$:.unshift "#{File.dirname(__FILE__)}/../lib"
require 'axlsx'
require 'perftools'
Axlsx.trust_input = true
row = []
# Taking worst case scenario of all string data
input = (32..126).to_a.pack('U*').chars.to_a
Expand Down

0 comments on commit 58e8dd8

Please sign in to comment.