rubyexcel 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,40 @@
1
1
  module RubyExcel
2
2
 
3
+ #
4
+ #Provides address translation methods to RubyExcel's classes
5
+ #
6
+
3
7
  module Address
4
8
 
9
+ #
10
+ # Translates an address to a column index
11
+ #
12
+ # @param [String] address the address to translate
13
+ # @return [Fixnum] the column index
14
+ #
15
+
5
16
  def address_to_col_index( address )
6
17
  col_index( column_id( address ) )
7
18
  end
8
19
 
20
+ #
21
+ # Translates an address to indices
22
+ #
23
+ # @param [String] address the address to translate
24
+ # @return [Array<Fixnum>] row index, column index
25
+ #
26
+
9
27
  def address_to_indices( address )
10
28
  [ row_id( address ), address_to_col_index( address ) ]
11
29
  end
12
30
 
31
+ #
32
+ # Translates a column id to an index
33
+ #
34
+ # @param [String] letter the column id to translate
35
+ # @return [Fixnum] the corresponding index
36
+ #
37
+
13
38
  def col_index( letter )
14
39
  return letter if letter.is_a? Fixnum
15
40
  letter !~ /[^A-Z]/ && [1,2,3].include?( letter.length ) or fail ArgumentError, "Invalid column reference: #{ letter }"
@@ -17,16 +42,37 @@ module RubyExcel
17
42
  loop { return idx if a == letter; idx+=1; a.next! }
18
43
  end
19
44
 
45
+ #
46
+ # Translates an index to a column letter
47
+ #
48
+ # @param [Fixnum] index the index to translate
49
+ # @return [String] the column letter
50
+ #
51
+
20
52
  def col_letter( index )
21
53
  return index if index.is_a? String
22
54
  index > 0 or fail ArgumentError, 'Indexing is 1-based'
23
55
  a = 'A'; ( index - 1 ).times { a.next! }; a
24
56
  end
25
57
 
58
+ #
59
+ # Translates an address to a column id
60
+ #
61
+ # @param [String] address the address to translate
62
+ # @return [String] the column id
63
+ #
64
+
26
65
  def column_id( address )
27
66
  address[/[A-Z]+/]
28
67
  end
29
68
 
69
+ #
70
+ # Expands an address to all contained addresses
71
+ #
72
+ # @param [String] address the address to translate
73
+ # @return [Array<String>] all addresses included within the given address
74
+ #
75
+
30
76
  def expand( address )
31
77
  return [[address]] unless address.include? ':'
32
78
  address.upcase.match( /([A-Z]+)(\d+):([A-Z]+)(\d+)/i )
@@ -34,27 +80,64 @@ module RubyExcel
34
80
  ( start_row..end_row ).map { |r| ( start_col..end_col ).map { |c| c + r.to_s } }
35
81
  end
36
82
 
83
+ #
84
+ # Translates indices to an address
85
+ #
86
+ # @param [Fixnum] row_idx the row index
87
+ # @param [Fixnum] column_idx the column index
88
+ # @return [String] the corresponding address
89
+ #
90
+
37
91
  def indices_to_address( row_idx, column_idx )
38
92
  [ row_idx, column_idx ].all? { |a| a.is_a?( Fixnum ) } or fail ArgumentError, 'Input must be Fixnum'
39
93
  col_letter( column_idx ) + row_idx.to_s
40
94
  end
41
95
 
96
+ #
97
+ # Checks whether an object is a multidimensional Array
98
+ #
99
+ # @param [Object] obj the object to test
100
+ # @return [Boolean] whether the object is a multidimensional Array
101
+ #
102
+
42
103
  def multi_array?( obj )
43
104
  obj.all? { |el| el.is_a?( Array ) } && obj.is_a?( Array ) rescue false
44
105
  end
45
106
 
107
+ #
108
+ # Offsets an address by row and column
109
+ #
110
+ # @param [String] address the address to offset
111
+ # @param [Fixnum] row the number of rows to offset by
112
+ # @param [Fixnum] col the number of columns to offset by
113
+ # @return [String] the new address
114
+ #
115
+
46
116
  def offset(address, row, col)
47
117
  ( col_letter( address_to_col_index( address ) + col ) ) + ( row_id( address ) + row ).to_s
48
118
  end
49
119
 
120
+ #
121
+ # Translates two objects to a range address
122
+ #
123
+ # @param [String, RubyExcel::Element] obj1 the first address element
124
+ # @param [String, RubyExcel::Element] obj2 the second address element
125
+ # @return [String] the new address
126
+ #
127
+
50
128
  def to_range_address( obj1, obj2 )
51
- if obj2
52
- obj2.is_a?( String ) ? ( obj1 + ':' + obj2 ) : "#{ obj1.address }:#{ obj2.address }"
53
- else
54
- obj1.is_a?( String ) ? obj1 : obj1.address
55
- end
129
+ addr = obj1.respond_to?( :address ) ? obj1.address : obj1.to_s
130
+ addr << ':' + ( obj2.respond_to?( :address ) ? obj2.address : obj2.to_s ) if obj2
131
+ addr
56
132
  end
57
133
 
134
+ #
135
+ # Translates an address to a row id
136
+ #
137
+ # @param [String] address the address to translate
138
+ # @return [Fixnum] the row id
139
+ #
140
+
58
141
  def row_id( address )
59
142
  address[/\d+/].to_i
60
143
  end
@@ -2,12 +2,30 @@ module RubyExcel
2
2
 
3
3
  require_relative 'address.rb'
4
4
 
5
+ #
6
+ # The class which holds a Sheet's data
7
+ #
8
+
5
9
  class Data
6
- attr_reader :rows, :cols
10
+ include Address
11
+ include Enumerable
12
+
13
+ #The number of rows in the data
14
+ attr_reader :rows
15
+
16
+ #The number of columns in the data
17
+ attr_reader :cols
18
+
19
+ #The parent Sheet
7
20
  attr_accessor :sheet
8
21
  alias parent sheet
9
22
 
10
- include Address
23
+ #
24
+ # Creates a RubyExcel::Data instance
25
+ #
26
+ # @param [RubyExcel::Sheet] sheet the parent Sheet
27
+ # @param [Array<Array>] input_data the multidimensional Array which holds the data
28
+ #
11
29
 
12
30
  def initialize( sheet, input_data )
13
31
  ( input_data.kind_of?( Array ) && input_data.all? { |el| el.kind_of?( Array ) } ) or fail ArgumentError, 'Input must be Array of Arrays'
@@ -16,37 +34,76 @@ require_relative 'address.rb'
16
34
  calc_dimensions
17
35
  end
18
36
 
37
+ #
38
+ # Returns a copy of the data
39
+ #
40
+
19
41
  def all
20
42
  @data.dup
21
43
  end
22
44
 
45
+ #
46
+ # Appends a multidimensional Array to the end of the data
47
+ #
48
+ # @param [Array<Array>] multi_array the multidimensional Array to add to the data
49
+ #
50
+
23
51
  def append( multi_array )
24
52
  @data << multi_array
25
53
  calc_dimensions
26
54
  end
27
55
 
56
+ #
57
+ # Finds a Column reference bu a header
58
+ #
59
+ # @param [String] header the header to search for
60
+ # @return [String] the Column reference
61
+ # @raise [NoMethodError] 'No header rows present'
62
+ # @raise [IndexError] header.to_s + ' is not a valid header'
63
+ #
64
+
28
65
  def colref_by_header( header )
66
+ return header.idx if header.is_a?( Column )
29
67
  sheet.header_rows > 0 or fail NoMethodError, 'No header rows present'
30
68
  @data[ 0..sheet.header_rows-1 ].each { |r| idx = r.index( header ); return col_letter( idx+1 ) if idx }
31
- fail IndexError, "#{ header } is not a valid header"
69
+ fail IndexError, header.to_s + ' is not a valid header'
32
70
  end
33
71
 
72
+ #
73
+ # Removes empty rows and columns from the data
74
+ #
75
+
34
76
  def compact!
35
77
  compact_columns!
36
78
  compact_rows!
37
79
  end
38
80
 
81
+ #
82
+ # Removes empty columns from the data
83
+ #
84
+
39
85
  def compact_columns!
40
86
  ensure_shape
41
87
  @data = @data.transpose.delete_if { |ar| ar.all? { |el| el.to_s.empty? } || ar.empty? }.transpose
42
88
  calc_dimensions
43
89
  end
44
90
 
91
+ #
92
+ # Removes empty rows from the data
93
+ #
94
+
45
95
  def compact_rows!
46
96
  @data.delete_if { |ar| ar.all? { |el| el.to_s.empty? } || ar.empty? }
47
97
  calc_dimensions
48
98
  end
49
99
 
100
+ #
101
+ # Deletes the data referenced by an object
102
+ #
103
+ # @param [RubyExcel::Column, RubyExcel::Element, RubyExcel::Row] object the object to delete
104
+ # @raise [NoMethodError] object.class.to_s + ' is not supported"
105
+ #
106
+
50
107
  def delete( object )
51
108
  case object
52
109
  when Row
@@ -60,41 +117,89 @@ require_relative 'address.rb'
60
117
  @data[ indices[0]..indices[2] ].each { |r| r.slice!( indices[1], indices[3] - indices[1] + 1 ) }
61
118
  @data.delete_if.with_index { |r,i| r.empty? && i.between?( indices[0], indices[2] ) }
62
119
  else
63
- fail NoMethodError, "#{ object.class } is not supported"
120
+ fail NoMethodError, object.class.to_s + ' is not supported'
64
121
  end
65
122
  calc_dimensions
66
123
  end
67
124
 
125
+ #
126
+ # Deletes the data referenced by a column id
127
+ #
128
+
68
129
  def delete_column( ref )
69
130
  delete( Column.new( sheet, ref ) )
70
131
  calc_dimensions
71
132
  end
72
133
 
134
+ #
135
+ # Deletes the data referenced by a row id
136
+ #
137
+
73
138
  def delete_row( ref )
74
139
  delete( Row.new( sheet, ref ) )
75
140
  calc_dimensions
76
141
  end
77
142
 
143
+ #
144
+ # Deletes the data referenced by an address
145
+ #
146
+
78
147
  def delete_range( ref )
79
148
  delete( Element.new( sheet, ref ) )
80
149
  calc_dimensions
81
150
  end
82
151
 
152
+ #
153
+ # Return a copy of self
154
+ #
155
+ # @return [RubyExcel::Data]
156
+ #
157
+
83
158
  def dup
84
159
  Data.new( sheet, @data.map(&:dup) )
85
160
  end
86
161
 
162
+ #
163
+ # Check whether the data is empty
164
+ #
165
+ # @return [Boolean]
166
+ #
167
+
87
168
  def empty?
88
169
  no_headers.empty?
89
170
  end
90
171
 
172
+ #
173
+ # Yields each "Row" as an Array
174
+ #
175
+
176
+ def each
177
+ @data.each { |ar| yield ar }
178
+ end
179
+
180
+ #
181
+ # Removes all Rows (omitting headers) where the block is false
182
+ #
183
+ # @param [String] header the header of the Column to pass to the block
184
+ # @yield [Object] the value at the intersection of Column and Row
185
+ # @return [self]
186
+ #
187
+
91
188
  def filter!( header )
92
189
  hrows = sheet.header_rows
93
- idx = col_index( hrows > 0 ? colref_by_header( header ) : header )
190
+ idx = index_by_header( header )
94
191
  @data = @data.select.with_index { |row, i| hrows > i || yield( row[ idx -1 ] ) }
95
192
  calc_dimensions
193
+ self
96
194
  end
97
195
 
196
+ #
197
+ # Select and re-order Columns by a list of headers
198
+ #
199
+ # @param [Array<String>] headers the ordered list of headers to keep
200
+ # @note Invalid headers will be skipped
201
+ #
202
+
98
203
  def get_columns!( *headers )
99
204
  headers = headers.flatten
100
205
  hrow = sheet.header_rows - 1
@@ -104,6 +209,32 @@ require_relative 'address.rb'
104
209
  calc_dimensions
105
210
  end
106
211
 
212
+ #
213
+ # Return the header section of the data
214
+ #
215
+
216
+ def headers
217
+ sheet.header_rows > 0 ? @data[ 0..sheet.header_rows-1 ] : nil
218
+ end
219
+
220
+ #
221
+ # Find a Column index by header
222
+ #
223
+ # @param [String] header the Column header to search for
224
+ # @return [Fixnum] the index of the given header
225
+ #
226
+
227
+ def index_by_header( header )
228
+ col_index( sheet.header_rows > 0 ? colref_by_header( header ) : header )
229
+ end
230
+
231
+ #
232
+ # Insert blank Columns into the data
233
+ #
234
+ # @param [String, Fixnum] before the Column reference to insert before.
235
+ # @param [Fixnum] number the number of new Columns to insert
236
+ #
237
+
107
238
  def insert_columns( before, number=1 )
108
239
  a = Array.new( number, nil )
109
240
  before = col_index( before ) - 1
@@ -111,45 +242,117 @@ require_relative 'address.rb'
111
242
  calc_dimensions
112
243
  end
113
244
 
245
+ #
246
+ # Insert blank Rows into the data
247
+ #
248
+ # @param [Fixnum] before the Row index to insert before.
249
+ # @param [Fixnum] number the number of new Rows to insert
250
+ #
251
+
114
252
  def insert_rows( before, number=1 )
115
253
  @data = @data.insert( ( col_index( before ) - 1 ), *Array.new( number, [nil] ) )
116
254
  calc_dimensions
117
255
  end
118
256
 
257
+ #
258
+ # Return the data without headers
259
+ #
260
+
119
261
  def no_headers
120
262
  @data[ sheet.header_rows..-1 ]
121
263
  end
122
264
 
265
+ #
266
+ # Split the data into two sections by evaluating each value in a column
267
+ #
268
+ # @param [String] header the header of the Column which contains the yield value
269
+ # @yield [value] yields the value of each row under the given header
270
+ #
271
+
272
+ def partition( header, &block )
273
+ idx = index_by_header( header )
274
+ d1, d2 = no_headers.partition { |row| yield row[ idx -1 ] }
275
+ [ headers + d1, headers + d2 ] if headers
276
+ end
277
+
278
+ #
279
+ # Read a value by address
280
+ #
281
+
123
282
  def read( addr )
124
283
  row_idx, col_idx = address_to_indices( addr )
125
284
  @data[ row_idx-1 ][ col_idx-1 ]
126
285
  end
127
286
  alias [] read
128
287
 
288
+ #
289
+ # Reverse the data Columns
290
+ #
291
+
129
292
  def reverse_columns!
130
293
  ensure_shape
131
294
  @data = @data.transpose.reverse.transpose
132
295
  end
296
+
297
+ #
298
+ # Reverse the data Rows (without affecting the headers)
299
+ #
133
300
 
134
301
  def reverse_rows!
135
302
  @data = skip_headers &:reverse
136
303
  end
304
+
305
+ #
306
+ # Perform an operation on the data without affecting the headers
307
+ #
308
+ # @yield [data] yield the data without the headers
309
+ # @return [Array<Array>] returns the data with the block operation performed on it, and the headers back in place
310
+ #
137
311
 
312
+ def skip_headers
313
+ return to_enum(:skip_headers) unless block_given?
314
+ hr = sheet.header_rows
315
+ if hr > 0
316
+ @data[ 0..hr - 1 ] + yield( @data[ hr..-1 ] )
317
+ else
318
+ yield( @data )
319
+ end
320
+ end
321
+
322
+ #
323
+ # Sort the data according to the block
324
+ #
325
+
138
326
  def sort!( &block )
139
327
  @data = skip_headers { |d| d.sort( &block ) }; self
140
328
  end
141
329
 
330
+ #
331
+ # Sort the data according to the block value
332
+ #
333
+
142
334
  def sort_by!( &block )
143
335
  @data = skip_headers { |d| d.sort_by( &block ) }
144
336
  end
145
337
 
338
+ #
339
+ # Unique the rows according to the values within a Column, selected by header
340
+ #
341
+
146
342
  def uniq!( header )
147
343
  column = col_index( colref_by_header( header ) )
148
- @data = @data.uniq { |row| row[ column - 1 ] }
344
+ @data = skip_headers { |d| d.uniq { |row| row[ column - 1 ] } }
149
345
  calc_dimensions
150
346
  end
151
347
  alias unique! uniq!
152
348
 
349
+ #
350
+ # Write a value into the data
351
+ #
352
+ # @param [String] addr the address to write the value to
353
+ # @param val the value to write to the address
354
+ #
355
+
153
356
  def write( addr, val )
154
357
  row_idx, col_idx = address_to_indices( addr )
155
358
  ( row_idx - rows ).times { @data << [] }
@@ -157,12 +360,6 @@ require_relative 'address.rb'
157
360
  calc_dimensions
158
361
  end
159
362
  alias []= write
160
-
161
- include Enumerable
162
-
163
- def each
164
- @data.each { |ar| yield ar }
165
- end
166
363
 
167
364
  private
168
365
 
@@ -175,15 +372,6 @@ require_relative 'address.rb'
175
372
  @data = @data.map { |ar| ar.length == cols ? ar : ar + Array.new( cols - ar.length, nil) }
176
373
  end
177
374
 
178
- def skip_headers
179
- hr = sheet.header_rows
180
- if hr > 0
181
- block_given? ? @data[ 0..hr - 1 ] + yield( @data[ hr..-1 ] ) : @data[ hr..-1 ]
182
- else
183
- block_given? ? yield( @data ) : @data
184
- end
185
- end
186
-
187
375
  end
188
376
 
189
377
  end
@@ -1,10 +1,32 @@
1
1
  module RubyExcel
2
2
 
3
3
  class Element
4
+ include Address
5
+ include Enumerable
4
6
 
5
- attr_reader :sheet, :address, :data, :column, :row
7
+ #The parent Sheet
8
+ attr_reader :sheet
6
9
  alias parent sheet
7
-
10
+
11
+ #The address
12
+ attr_reader :address
13
+
14
+ #The Data underlying the Sheet
15
+ attr_reader :data
16
+
17
+ #The first Column id in the address
18
+ attr_reader :column
19
+
20
+ #The first Row id in the address
21
+ attr_reader :row
22
+
23
+ #
24
+ # Creates a RubyExcel::Element instance
25
+ #
26
+ # @param [RubyExcel::Sheet] sheet the parent Sheet
27
+ # @param [String] addr the address to reference
28
+ #
29
+
8
30
  def initialize( sheet, addr )
9
31
  fail ArgumentError, "Invalid range: #{ addr }" unless addr =~ /\A[A-Z]+\d+:[A-Z]+\d+\z|\A[A-Z]+\d+\z/
10
32
  @sheet = sheet
@@ -13,17 +35,31 @@ module RubyExcel
13
35
  @column = column_id( addr )
14
36
  @row = row_id( addr )
15
37
  end
38
+
39
+ #
40
+ # Delete the data referenced by self.address
41
+ #
16
42
 
17
43
  def delete
18
44
  data.delete( self )
19
45
  end
20
-
21
- include Address
46
+
47
+ #
48
+ # Return the value at this Element's address
49
+ #
50
+ # @return [Object, Array<Object>] the Object or Array of Objects within the data, referenced by the address
51
+ #
22
52
 
23
53
  def value
24
54
  address.include?( ':' ) ? expand( address ).map { |ar| ar.map { |addr| data[ addr ] } } : data[ address ]
25
55
  end
26
56
 
57
+ #
58
+ # Set the value at this Element's address
59
+ #
60
+ # @param [Object, Array<Object>] val the Object or Array of Objects to write into the data
61
+ #
62
+
27
63
  def value=( val )
28
64
  if address.include? ':'
29
65
  if multi_array?( val )
@@ -36,30 +72,55 @@ module RubyExcel
36
72
  end
37
73
  self
38
74
  end
75
+
76
+ #
77
+ # The data at address as a TSV String
78
+ #
39
79
 
40
80
  def to_s
41
81
  value.is_a?( Array ) ? value.map { |ar| ar.join "\t" }.join($/) : value.to_s
42
82
  end
43
83
 
84
+ #
85
+ # View the object for debugging
86
+ #
87
+
44
88
  def inspect
45
- "#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ address }"
89
+ "#{ self.class }:0x#{ '%x' % ( object_id << 1 ) }: #{ address }"
46
90
  end
47
91
 
48
- include Enumerable
92
+ #
93
+ # Yields each value in the data referenced by the address
94
+ #
49
95
 
50
96
  def each
97
+ return to_enum( :each ) unless block_given?
51
98
  expand( address ).flatten.each { |addr| yield data[ addr ] }
52
99
  end
53
100
 
101
+ #
102
+ # Yields each Element referenced by the address
103
+ #
104
+
54
105
  def each_cell
106
+ return to_enum( :each_cell ) unless block_given?
55
107
  expand( address ).flatten.each { |addr| yield Element.new( sheet, addr ) }
56
108
  end
57
109
 
110
+ #
111
+ # Checks whether the data referenced by the address is empty
112
+ #
113
+
58
114
  def empty?
59
115
  all? { |v| v.to_s.empty? }
60
116
  end
61
117
 
118
+ #
119
+ # Replaces each value with the result of the block
120
+ #
121
+
62
122
  def map!
123
+ return to_enum( :map! ) unless block_given?
63
124
  expand( address ).flatten.each { |addr| data[ addr ] = yield data[ addr ] }
64
125
  end
65
126