Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small performance improvements #438

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/axlsx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ def self.cell_r(c_index, r_index)
# @param [String] range A cell range, for example A1:D5
# @return [Array]
def self.range_to_a(range)
range.match(/^(\w+?\d+)\:(\w+?\d+)$/)
start_col, start_row = name_to_indices($1)
end_col, end_row = name_to_indices($2)
start_of_range, end_of_range = range.split(":")
start_col, start_row = name_to_indices(start_of_range)
end_col, end_row = name_to_indices(end_of_range)
(start_row..end_row).to_a.map do |row_num|
(start_col..end_col).to_a.map do |col_num|
cell_r(col_num, row_num)
Expand Down
5 changes: 4 additions & 1 deletion lib/axlsx/package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ def workbook
#end

# @see workbook
def workbook=(workbook) DataTypeValidator.validate :Package_workbook, Workbook, workbook; @workbook = workbook; end
def workbook=(workbook)
DataTypeValidator.validate(:Package_workbook, Workbook, workbook)
@workbook = workbook
end

# Serialize your workbook to disk as an xlsx document.
#
Expand Down
30 changes: 21 additions & 9 deletions lib/axlsx/util/simple_typed_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ def to_ary
# one of the allowed types
# @return [SimpleTypedList]
def +(v)
v.each do |item|
DataTypeValidator.validate :SimpleTypedList_plus, @allowed_types, item
@list << item
v.each do |item|
validate_element(item)
@list << item
end
end

Expand All @@ -93,7 +93,7 @@ def +(v)
# @raise [ArgumentError] if the value being added is not one fo the allowed types
# @return [Integer] returns the index of the item added.
def <<(v)
DataTypeValidator.validate :SimpleTypedList_push, @allowed_types, v
validate_element(v)
@list << v
@list.size - 1
end
Expand Down Expand Up @@ -126,19 +126,32 @@ def delete_at(index)
# @raise [ArgumentError] if the index is protected by locking
# @raise [ArgumentError] if the item is not one of the allowed types
def []=(index, v)
DataTypeValidator.validate :SimpleTypedList_insert, @allowed_types, v
validate_element(v)
raise ArgumentError, "Item is protected and cannot be changed" if protected? index
@list[index] = v
v
end

def validate_element(v)
if @allowed_types.size == 1
unless v.is_a?(@allowed_types.first)
raise ArgumentError, "element should be instance of #{@allowed_types.first} given #{v.class}"
end
else
@allowed_types.each do |klass|
return if v.is_a?(klass)
end
raise ArgumentError, "element should be instance of #{@allowed_types.inspect} given #{v.class}"
end
end

# inserts an item at the index specfied
# @param [Integer] index
# @param [Any] v
# @raise [ArgumentError] if the index is protected by locking
# @raise [ArgumentError] if the index is not one of the allowed types
def insert(index, v)
DataTypeValidator.validate :SimpleTypedList_insert, @allowed_types, v
validate_element(v)
raise ArgumentError, "Item is protected and cannot be changed" if protected? index
@list.insert(index, v)
v
Expand All @@ -147,8 +160,7 @@ def insert(index, v)
# determines if the index is protected
# @param [Integer] index
def protected? index
return false unless locked_at.is_a? Fixnum
index < locked_at
return index < (defined?(@locked_at) && @locked_at || -1)
end

DESTRUCTIVE = ['replace', 'insert', 'collect!', 'map!', 'pop', 'delete_if',
Expand All @@ -164,7 +176,7 @@ def #{method}(*args, &block)
end
}
end

def to_xml_string(str = '')
classname = @allowed_types[0].name.split('::').last
el_name = serialize_as.to_s || (classname[0,1].downcase + classname[1..-1])
Expand Down
41 changes: 29 additions & 12 deletions lib/axlsx/util/validators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,30 @@ def self.validate(name, regex, v)

# Validate that the class of the value provided is either an instance or the class of the allowed types and that any specified additional validation returns true.
class DataTypeValidator
class << self
attr_accessor :disable
end

# Perform validation
# @param [String] name The name of what is being validated. This is included in the error message
# @param [Array, Class] types A single class or array of classes that the value is validated against.
# @param [Block] other Any block that must evaluate to true for the value to be valid
# @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=false)
def self.validate(name, types, v, other = false)
return if defined?(@disable) && @disable

if other.is_a?(Proc)
raise ArgumentError, (ERR_TYPE % [v.inspect, name, types.inspect]) unless other.call(v)
end
v_class = v.is_a?(Class) ? v : v.class
Array(types).each do |t|
Array(types).each do |t|
return if v_class <= t
end
raise ArgumentError, (ERR_TYPE % [v.inspect, name, types.inspect])
end
end


# Requires that the value can be converted to an integer
# @para, [Any] v the value to validate
Expand All @@ -78,49 +83,61 @@ def self.validate_integerish(v)
def self.validate_angle(v)
raise ArgumentError, (ERR_ANGLE % v.inspect) unless (v.to_i >= -5400000 && v.to_i <= 5400000)
end

UINT_VALIDATOR = lambda { |arg| arg.respond_to?(:>=) && arg >= 0 }


# Requires that the value is a Fixnum or Integer and is greater or equal to 0
# @param [Any] v The value validated
# @raise [ArgumentError] raised if the value is not a Fixnum or Integer value greater or equal to 0
# @return [Boolean] true if the data is valid
def self.validate_unsigned_int(v)
DataTypeValidator.validate(:unsigned_int, Integer, v, UINT_VALIDATOR)
if !v.is_a?(Integer) || v < 0
raise ArgumentError, (ERR_TYPE % [v.inspect, :unsigned_int, [Integer].inspect])
end
end

# Requires that the value is a Fixnum Integer or Float and is greater or equal to 0
# @param [Any] v The value validated
# @raise [ArgumentError] raised if the value is not a Fixnun, Integer, Float value greater or equal to 0
# @return [Boolean] true if the data is valid
def self.validate_unsigned_numeric(v)
DataTypeValidator.validate(:unsigned_numeric, Numeric, v, UINT_VALIDATOR)
if !v.is_a?(Numeric) || v < 0
raise ArgumentError, (ERR_TYPE % [v.inspect, :unsigned_int, [Numeric].inspect])
end
end

# Requires that the value is a Integer
# @param [Any] v The value validated
def self.validate_int(v)
DataTypeValidator.validate :signed_int, Integer, v
unless v.is_a?(Integer)
raise ArgumentError, (ERR_TYPE % [v.inspect, :signed_int, [Integer].inspect])
end
end

BOOLEAN_VALUES = [0, 1, "true", "false", :true, :false, true, false, "0", "1"]

# Requires that the value is a form that can be evaluated as a boolean in an xml document.
# The value must be an instance of Fixnum, String, Integer, Symbol, TrueClass or FalseClass and
# it must be one of 0, 1, "true", "false", :true, :false, true, false, "0", or "1"
# @param [Any] v The value validated
def self.validate_boolean(v)
DataTypeValidator.validate(:boolean, [String, Integer, Symbol, TrueClass, FalseClass], v, lambda { |arg| [0, 1, "true", "false", :true, :false, true, false, "0", "1"].include?(arg) })
unless BOOLEAN_VALUES.include?(v)
raise ArgumentError, (ERR_TYPE % [v.inspect, :boolean, [String, Integer, Symbol, TrueClass, FalseClass].inspect])
end
end

# Requires that the value is a String
# @param [Any] v The value validated
def self.validate_string(v)
DataTypeValidator.validate :string, String, v
unless v.is_a?(String)
raise ArgumentError, (ERR_TYPE % [v.inspect, :string, [String].inspect])
end
end

# Requires that the value is a Float
# @param [Any] v The value validated
def self.validate_float(v)
DataTypeValidator.validate :float, Float, v
unless v.is_a?(Float)
raise ArgumentError, (ERR_TYPE % [v.inspect, :float, [Float].inspect])
end
end

# Requires that the value is a string containing a positive decimal number followed by one of the following units:
Expand Down
9 changes: 5 additions & 4 deletions lib/axlsx/workbook/worksheet/cell_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ class << self
# @param [Integer] column_index The index of the cell's column
# @param [String] str The string to apend serialization to.
# @return [String]
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 << '" ')

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
self.send(method, cell, str)
str << '</c>'
end
end

# builds an xml text run based on this cells attributes.
# @param [String] str The string instance this run will be concated to.
Expand All @@ -28,7 +29,7 @@ def run_xml_string(cell, str = '')
elsif cell.contains_rich_text?
cell.value.to_xml_string(str)
else
str << ('<t>' << cell.clean_value << '</t>')
str << "<t>#{cell.clean_value}</t>"
end
str
end
Expand Down
7 changes: 5 additions & 2 deletions lib/axlsx/workbook/worksheet/date_time_converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ module Axlsx
# The DateTimeConverter class converts both data and time types to their apprpriate excel serializations
class DateTimeConverter

EPOCH_1904 = 24107.0 # number of days from 1904-01-01 to 1970-01-01
EPOCH_1900 = 25569.0 # number of days from 1899-12-30 to 1970-01-01

# The date_to_serial method converts Date objects to the equivelant excel serialized forms
# @param [Date] date the date to be serialized
# @return [Numeric]
def self.date_to_serial(date)
epoch = Axlsx::Workbook::date1904 ? Date.new(1904) : Date.new(1899, 12, 30)
epoch = Axlsx::Workbook::date1904 ? EPOCH_1904 : EPOCH_1900
offset_date = date.respond_to?(:utc_offset) ? date + date.utc_offset.seconds : date
(offset_date - epoch).to_f
offset_date.strftime("%s").to_f / 86400.0 + epoch
end

# The time_to_serial methond converts a Time object its excel serialized form.
Expand Down
4 changes: 3 additions & 1 deletion lib/axlsx/workbook/worksheet/row.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ def worksheet=(v) DataTypeValidator.validate :row_worksheet, Worksheet, v; @work
def array_to_cells(values, options={})
DataTypeValidator.validate :array_to_cells, Array, values
types, style, formula_values = options.delete(:types), options.delete(:style), options.delete(:formula_values)
values.each_with_index do |value, index|

values.size.times do |index|
value = values[index]
options[:style] = style.is_a?(Array) ? style[index] : style if style
options[:type] = types.is_a?(Array) ? types[index] : types if types
options[:formula_value] = formula_values[index] if formula_values.is_a?(Array)
Expand Down
7 changes: 6 additions & 1 deletion test/benchmark.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
require 'axlsx'
require 'csv'
require 'benchmark'

Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

Axlsx::trust_input = true
row = []
input = (32..126).to_a.pack('U*').chars.to_a
Expand All @@ -12,14 +16,14 @@

x.report('axlsx_noautowidth') {
p = Axlsx::Package.new
p.use_autowidth = false
p.workbook do |wb|
wb.add_worksheet do |sheet|
times.times do
sheet << row
end
end
end
p.use_autowidth = false
p.serialize("example_noautowidth.xlsx")
}

Expand Down Expand Up @@ -69,4 +73,5 @@
end
}
end

File.delete("example.csv", "example_streamed.xlsx", "example_shared.xlsx", "example_autowidth.xlsx", "example_noautowidth.xlsx")