class RubyExcel # Helper methods for RubyExcel classes module Helpers # Column numeric reference point (index starts at 1) ColumnRefs = ( 'A'..'ZZZ' ).to_a.unshift nil # Extract a column ID from a string # # @param [String] string the address to extract the column from # @return [String] the column reference of the given address def column_id( string ) "#{string}"[/[A-Z]+/] end # Extract a row ID from a string # # @param [String] string the address to extract the row from # @return [Fixnum] the row number of the given address def row_id( string ) "#{string}"[/\d+/].to_i end # Check that an object is a multidimensional array # # @param [Object] obj the object to test # @return [Boolean] true if the object is a multidimensional array, false otherwise def multi_array?( obj ) obj.all? { |el| el.is_a?( Array ) } && obj.is_a?( Array ) rescue false end # Switch between column number and letter # # @param [String, Fixnum] ref A number or letter corresponding to a column reference # @return [String, Fixnum] the column reference in the opposite format ( Number <-> Letter ) def col_val( ref ) ref.is_a?( Fixnum ) ? ColumnRefs[ ref ] : ColumnRefs.index( ref ) end # Offset an address # # @param [String] addr the address to offset # @param [Fixnum] row_num the number of rows to shift by # @param [Fixnum] col_num the number of columns to shift by # @return [String] the modified address def offset( addr, row_num=0, col_num=0 ) c = col_val( column_id( addr ) ) + col_num r = row_id( addr ) + row_num fail ArgumentError, "Invalid Column Offset: #{ addr }, #{ col_num }: #{ col_val( c ) }" unless c > 0 fail ArgumentError, "Invalid Row Offset: #{ addr }, #{ row_num }: #{ r }" unless r > 0 "#{ col_val( c ) }#{ r }" end end # Parent of Row or Column. Not indended for seperate use. class Section attr_reader :ruby_excel def initialize( ruby_excel ) @ruby_excel = ruby_excel end include Helpers #Retreive a value by address # # @param [String, Fixnum] item the index at which to locate the value def [](item) ruby_excel[ indexes( item ) ] end #Set an address to a value # # @param [String, Fixnum] item the index at which to locate the value # @param [Object] val the value to set the index to def []=(item, val) ruby_excel[ indexes( item ) ] = val end def first self.is_a?( Row ) ? self[ 'A' ] : self[ 2 ] end def last self.is_a?( Row ) ? self[ column_id( ruby_excel.maxcolumn ) ] : self[ ruby_excel.maxrow ] end #Find a value # # @yield [Object] yields each value to the block # @return [String, nil] the address of the value or nil def find each { |v,k| return k if yield v }; nil end def inspect "Class: #{ self.class }, Index: #{ index }" end def empty? each { |val| return false unless val.nil? } true end def to_a each.map { |v,_| v } end def to_s delim = ( self.is_a?( Row ) ? "\t" : "\n" ) to_a.join( delim ) end def dup ruby_XL = ruby_excel.dup ruby_XL.send( self.class.to_sym, self.index ,ruby_XL ) end include Enumerable # each Enumerator # # @yield [value, address] Gives the block the cell value and its address # @return [Enumerable, Row, Column] enumerable if no block is given, self if block is executed def each return to_enum(:each) unless block_given? each_address do |addr| yield ruby_excel[ addr ], addr end self end # map Enumerator # # @yield [value, address] Gives the block the cell value and its address # @return [Column, Row] returns self with each value set to the block value def map! each_address { |addr| ruby_excel[ addr ] = yield ruby_excel[ addr ], addr } self end # map Enumerator # # @yield [value, address] Gives the block the cell value and its address # @return [Column, Row] returns a copy of self with each value set to the block value def map( &block ) dup.map!( &block ) end def reverse_each reverse_address do |addr| yield ruby_excel[ addr ], addr end self end def addresses ary = [] each_address { |addr| ary << addr } ary end def values ary = [] each { |val, _| ary << val } ary end end # A Row of RubyExcel class Row < Section def initialize( ruby_excel, row_ID ) @row_ID = row_ID.to_i super ruby_excel end def index @row_ID end def +(other) if other.class == Row RubyExcel.new.load [ self.to_a, other.to_a ] elsif other.class == RubyExcel other.dup.insert_rows( [ self.to_a ], other.maxrow.next ) else fail ArgumentError, "Unsupported class: #{ other.class }" end end def <<( other ) if @row_ID == 1 ruby_excel[ "#{ col_val( ruby_excel.maxcolumn.next ) }#{ @row_ID }" ] = other else ruby_excel[ "#{ col_val( ruby_excel.maxcolumn ) }#{ @row_ID }" ] = other end end def getref( header ) ruby_excel.getref( header ) end private def indexes( col_id ) "#{col_id}#{@row_ID}" end def each_address ( 'A'..col_val( ruby_excel.maxcolumn ) ).each { |col_id| yield "#{col_id}#{@row_ID}" } self end def reverse_address ( col_val( ruby_excel.maxcolumn )..'A' ).each { |col_id| yield "#{col_id}#{@row_ID}" } self end end # A Column of RubyExcel class Column < Section def initialize( ruby_excel, col_ID ) @col_ID = col_ID super ruby_excel end def index @col_ID end def +(other) if other.class == Column other = other.to_a RubyExcel.new.load to_a.map.with_index { |el, idx| [ el, other[idx] ] } elsif other.class == RubyExcel other.dup.insert_columns( [ self.to_a ].transpose, col_val( other.maxcolumn ).next ) else fail ArgumentError, "Unsupported class: #{ other.class }" end end def <<( other ) if @col_ID == 'A' ruby_excel[ "#{ @col_ID }#{ ruby_excel.maxrow.next }" ] = other else ruby_excel[ "#{ @col_ID }#{ ruby_excel.maxrow }" ] = other end end private def indexes( row_id ) "#{@col_ID}#{row_id}" end def each_address 1.upto( ruby_excel.maxrow ) { |row_id| yield "#{@col_ID}#{row_id}" } self end def reverse_address ruby_excel.maxrow.downto(1) { |row_id| yield "#{@col_ID}#{row_id}" } self end end # Element (Cell / Range) of RubyExcel class Element attr_reader :ruby_excel, :address def initialize( ruby_excel, addr ) @ruby_excel = ruby_excel @address = addr end include Helpers # Retrieve value(s) # # @return [String, Array] def value if address.include? ':' expand.map { |ar| ar.map { |addr| ruby_excel[ addr ] } } else ruby_excel[ address ] end end # Set value(s) # # @param [Object, Array] val def value=( val ) if address.include? ':' if multi_array?( val ) addresses = expand fail ArgumentError, "Invalid dimensions #{ val_dim.inspect } : #{ rng_dim.inspect }" unless ( ( val_dim = [ val.max_by(&:length).length, val.length ] ) == ( rng_dim = [ addresses.max_by(&:length).length, addresses.length ] ) ) addresses.each_with_index { |row,idx| row.each_with_index { |el,i| ruby_excel[ el ] = val[idx][i] } } else expand.each { |ar| ar.each { |addr| ruby_excel[ addr ] = val } } end else ruby_excel[ address ] = val end self end def to_s if address.include? ':' value.map { |ar| ar.join("\t") }.join $/ else value.to_s end end def inspect "Class: #{self.class.name}, Address: #{address}" end private def expand fail ArgumentError, "Invalid range: #{ address }" unless address =~ /\A[A-Z]+\d+:[A-Z]+\d+\z/ start_col, start_row, end_col, end_row = address[/^[A-Z]+/], address.match(/(\d+):/).captures.first, address[/(?<=:)[A-Z]+/], address[/\d+$/] start_row, end_row = end_row, start_row unless start_row <= end_row start_col, end_col = end_col, start_col unless start_col <= end_col (start_row..end_row).to_a.map { |r| (start_col..end_col).to_a.map { |c| "#{ c }#{ r }" } } end end end