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")