diff --git a/lib/axlsx.rb b/lib/axlsx.rb
index c5d26c2b..7ae23c2f 100644
--- a/lib/axlsx.rb
+++ b/lib/axlsx.rb
@@ -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)
diff --git a/lib/axlsx/package.rb b/lib/axlsx/package.rb
index 8a4f29dc..0072ff19 100644
--- a/lib/axlsx/package.rb
+++ b/lib/axlsx/package.rb
@@ -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.
#
diff --git a/lib/axlsx/util/simple_typed_list.rb b/lib/axlsx/util/simple_typed_list.rb
index 95f5b3f2..22d87b04 100644
--- a/lib/axlsx/util/simple_typed_list.rb
+++ b/lib/axlsx/util/simple_typed_list.rb
@@ -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
@@ -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
@@ -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
@@ -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',
@@ -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])
diff --git a/lib/axlsx/util/validators.rb b/lib/axlsx/util/validators.rb
index 9c6f34aa..449180a5 100644
--- a/lib/axlsx/util/validators.rb
+++ b/lib/axlsx/util/validators.rb
@@ -44,6 +44,10 @@ 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.
@@ -51,18 +55,19 @@ 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=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
@@ -78,15 +83,15 @@ 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
@@ -94,33 +99,45 @@ def self.validate_unsigned_int(v)
# @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:
diff --git a/lib/axlsx/workbook/worksheet/cell_serializer.rb b/lib/axlsx/workbook/worksheet/cell_serializer.rb
index 9a9f9465..25287f75 100644
--- a/lib/axlsx/workbook/worksheet/cell_serializer.rb
+++ b/lib/axlsx/workbook/worksheet/cell_serializer.rb
@@ -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 << ('' if cell.value.nil?
method = cell.type
self.send(method, cell, str)
str << ''
- end
+ end
# builds an xml text run based on this cells attributes.
# @param [String] str The string instance this run will be concated to.
@@ -28,7 +29,7 @@ def run_xml_string(cell, str = '')
elsif cell.contains_rich_text?
cell.value.to_xml_string(str)
else
- str << ('' << cell.clean_value << '')
+ str << "#{cell.clean_value}"
end
str
end
diff --git a/lib/axlsx/workbook/worksheet/date_time_converter.rb b/lib/axlsx/workbook/worksheet/date_time_converter.rb
index 44f9fec4..cf2c0ccc 100644
--- a/lib/axlsx/workbook/worksheet/date_time_converter.rb
+++ b/lib/axlsx/workbook/worksheet/date_time_converter.rb
@@ -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.
diff --git a/lib/axlsx/workbook/worksheet/row.rb b/lib/axlsx/workbook/worksheet/row.rb
index 078e281f..18094e83 100644
--- a/lib/axlsx/workbook/worksheet/row.rb
+++ b/lib/axlsx/workbook/worksheet/row.rb
@@ -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)
diff --git a/test/benchmark.rb b/test/benchmark.rb
old mode 100644
new mode 100755
index b1162591..aa713183
--- a/test/benchmark.rb
+++ b/test/benchmark.rb
@@ -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
@@ -12,6 +16,7 @@
x.report('axlsx_noautowidth') {
p = Axlsx::Package.new
+ p.use_autowidth = false
p.workbook do |wb|
wb.add_worksheet do |sheet|
times.times do
@@ -19,7 +24,6 @@
end
end
end
- p.use_autowidth = false
p.serialize("example_noautowidth.xlsx")
}
@@ -69,4 +73,5 @@
end
}
end
+
File.delete("example.csv", "example_streamed.xlsx", "example_shared.xlsx", "example_autowidth.xlsx", "example_noautowidth.xlsx")