rubyexcel 0.1.2 → 0.1.3
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.
- data/lib/README.md +19 -8
- data/lib/rubyexcel/rubyexcel_components.rb +2 -0
- data/lib/rubyexcel/section.rb +27 -32
- data/lib/rubyexcel/sheet.rb +582 -0
- data/lib/rubyexcel.rb +0 -608
- metadata +3 -2
data/lib/README.md
CHANGED
@@ -137,20 +137,28 @@ Common Operations
|
|
137
137
|
--------
|
138
138
|
|
139
139
|
```ruby
|
140
|
+
#Some data to play with
|
141
|
+
s = RubyExcel.sample_sheet
|
142
|
+
|
143
|
+
#Have a look at the data
|
144
|
+
puts s
|
145
|
+
|
140
146
|
#Append a Column by adding a header
|
141
|
-
s << '
|
142
|
-
x = 1
|
147
|
+
s << 'Number'
|
143
148
|
|
144
149
|
#Iterate through the rest of the rows while appending data
|
150
|
+
x = 1
|
145
151
|
s.rows(2) { |row| row << x; x+=1 }
|
146
152
|
|
147
|
-
#
|
153
|
+
#Filter to specific part numbers
|
154
|
+
s.filter!( 'Part', &/Type[1-3]/ )
|
155
|
+
|
156
|
+
#Add the Number to the Cost in each row.
|
157
|
+
s.rows(2) { |row| row.cell_h('Cost').value += row.cell_h('Number').value }
|
148
158
|
|
149
159
|
#Split the data into multiple sheets by part number
|
150
160
|
wb = s.split( 'Part' )
|
151
161
|
|
152
|
-
#Will add more examples here later.
|
153
|
-
|
154
162
|
```
|
155
163
|
|
156
164
|
Workbook
|
@@ -284,9 +292,8 @@ type_1_and_3, other = s.partition( 'Part', &/Type[13]/ )
|
|
284
292
|
s.reverse_rows!
|
285
293
|
s.reverse_columns!
|
286
294
|
|
287
|
-
#Sort the rows by
|
288
|
-
s.
|
289
|
-
s.sort_by! { |r| r['A'] }
|
295
|
+
#Sort the rows by a header (ignores header rows)
|
296
|
+
s.sort_by!( 'Part' )
|
290
297
|
|
291
298
|
#Split a Sheet into a Workbook of Sheets by a column (selected by header)
|
292
299
|
wb = s.split( 'Part' )
|
@@ -334,6 +341,10 @@ s.rows(2) { |r| r << 'Column' }
|
|
334
341
|
s.column(1) << 'New'
|
335
342
|
s.columns(2) { |c| c << 'Row' }
|
336
343
|
|
344
|
+
#Access a cell by column header (Row only)
|
345
|
+
s.row(2).cell_by_header( 'Part' ) #=> Element A2
|
346
|
+
s.row(2).cell_h( 'Cost' ) #=> Element E2
|
347
|
+
|
337
348
|
#Delete the data referenced by self.
|
338
349
|
row.delete
|
339
350
|
col.delete
|
data/lib/rubyexcel/section.rb
CHANGED
@@ -35,11 +35,7 @@ module RubyExcel
|
|
35
35
|
#
|
36
36
|
|
37
37
|
def <<( value )
|
38
|
-
|
39
|
-
when Row ; lastone = ( idx == 1 ? data.cols + 1 : data.cols )
|
40
|
-
else ; lastone = ( idx == 'A' ? data.rows + 1 : data.rows )
|
41
|
-
end
|
42
|
-
data[ translate_address( lastone ) ] = value
|
38
|
+
data[ translate_address( ( col_index( idx ) == 1 ? data.cols + 1 : data.cols ) ) ] = value
|
43
39
|
end
|
44
40
|
|
45
41
|
#
|
@@ -58,7 +54,7 @@ module RubyExcel
|
|
58
54
|
data.delete( self ); self
|
59
55
|
end
|
60
56
|
|
61
|
-
|
57
|
+
#
|
62
58
|
# Yields each value
|
63
59
|
#
|
64
60
|
|
@@ -73,7 +69,7 @@ module RubyExcel
|
|
73
69
|
|
74
70
|
def each_without_headers
|
75
71
|
return to_enum( :each_without_headers ) unless block_given?
|
76
|
-
|
72
|
+
each_address( false ) { |addr| yield data[ addr ] }
|
77
73
|
end
|
78
74
|
alias each_wh each_without_headers
|
79
75
|
|
@@ -92,7 +88,7 @@ module RubyExcel
|
|
92
88
|
|
93
89
|
def each_cell_without_headers
|
94
90
|
return to_enum( :each_cell_without_headers ) unless block_given?
|
95
|
-
|
91
|
+
each_address( false ) { |addr| yield Element.new( sheet, addr ) }
|
96
92
|
end
|
97
93
|
alias each_cell_wh each_cell_without_headers
|
98
94
|
|
@@ -149,7 +145,7 @@ module RubyExcel
|
|
149
145
|
|
150
146
|
def map_without_headers!
|
151
147
|
return to_enum( :map_without_headers! ) unless block_given?
|
152
|
-
|
148
|
+
each_address( false ) { |addr| data[addr] = ( yield data[addr] ) }
|
153
149
|
end
|
154
150
|
|
155
151
|
#
|
@@ -194,19 +190,6 @@ module RubyExcel
|
|
194
190
|
end
|
195
191
|
alias []= write
|
196
192
|
|
197
|
-
private
|
198
|
-
|
199
|
-
def translate_address( addr )
|
200
|
-
case self
|
201
|
-
when Row
|
202
|
-
col_letter( addr ) + idx.to_s
|
203
|
-
when Column
|
204
|
-
addr = addr.to_s unless addr.is_a?( String )
|
205
|
-
fail ArgumentError, "Invalid address : #{ addr }" if addr =~ /[^\d]/
|
206
|
-
idx + addr
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
193
|
end
|
211
194
|
|
212
195
|
#
|
@@ -252,12 +235,16 @@ module RubyExcel
|
|
252
235
|
|
253
236
|
def getref( header )
|
254
237
|
sheet.header_rows.times do |t|
|
255
|
-
res = sheet.row( t + 1 ).find
|
238
|
+
res = sheet.row( t + 1 ).find { |v| v == header }
|
256
239
|
return column_id( res ) if res
|
257
240
|
end
|
258
241
|
fail ArgumentError, 'Invalid header: ' + header.to_s
|
259
242
|
end
|
260
243
|
|
244
|
+
#
|
245
|
+
# The number of Columns in the Row
|
246
|
+
#
|
247
|
+
|
261
248
|
def length
|
262
249
|
data.cols
|
263
250
|
end
|
@@ -276,12 +263,15 @@ module RubyExcel
|
|
276
263
|
|
277
264
|
private
|
278
265
|
|
279
|
-
def each_address
|
266
|
+
def each_address( unused=nil )
|
280
267
|
return to_enum( :each_address ) unless block_given?
|
281
|
-
( 'A'..col_letter( data.cols ) ).each { |col_id| yield
|
268
|
+
( 'A'..col_letter( data.cols ) ).each { |col_id| yield translate_address( col_id ) }
|
282
269
|
end
|
283
|
-
alias each_address_without_headers each_address
|
284
270
|
|
271
|
+
def translate_address( addr )
|
272
|
+
col_letter( addr ) + idx.to_s
|
273
|
+
end
|
274
|
+
|
285
275
|
end
|
286
276
|
|
287
277
|
#
|
@@ -307,22 +297,27 @@ module RubyExcel
|
|
307
297
|
super( sheet )
|
308
298
|
end
|
309
299
|
|
300
|
+
#
|
301
|
+
# The number of Rows in the Column
|
302
|
+
#
|
303
|
+
|
310
304
|
def length
|
311
305
|
data.rows
|
312
306
|
end
|
313
307
|
|
314
308
|
private
|
315
309
|
|
316
|
-
def each_address
|
310
|
+
def each_address( headers=true )
|
317
311
|
return to_enum( :each_address ) unless block_given?
|
318
|
-
( 1
|
312
|
+
( headers ? 1 : sheet.header_rows + 1 ).upto( data.rows ) { |row_id| yield translate_address( row_id ) }
|
319
313
|
end
|
320
314
|
|
321
|
-
def
|
322
|
-
|
323
|
-
|
315
|
+
def translate_address( addr )
|
316
|
+
addr = addr.to_s unless addr.is_a?( String )
|
317
|
+
fail ArgumentError, "Invalid address : #{ addr }" if addr =~ /[^\d]/
|
318
|
+
idx + addr
|
324
319
|
end
|
325
|
-
|
320
|
+
|
326
321
|
end
|
327
322
|
|
328
323
|
end
|
@@ -0,0 +1,582 @@
|
|
1
|
+
#
|
2
|
+
# Namespace for all RubyExcel Classes and Modules
|
3
|
+
#
|
4
|
+
|
5
|
+
module RubyExcel
|
6
|
+
|
7
|
+
#
|
8
|
+
# The front-end class for data manipulation and output.
|
9
|
+
#
|
10
|
+
|
11
|
+
class Sheet
|
12
|
+
include Address
|
13
|
+
|
14
|
+
# The Data underlying the Sheet
|
15
|
+
attr_reader :data
|
16
|
+
|
17
|
+
# The name of the Sheet
|
18
|
+
attr_accessor :name
|
19
|
+
|
20
|
+
# The number of rows treated as headers
|
21
|
+
attr_accessor :header_rows
|
22
|
+
|
23
|
+
# The Workbook parent of this Sheet
|
24
|
+
attr_accessor :workbook
|
25
|
+
|
26
|
+
alias parent workbook; alias parent= workbook=
|
27
|
+
alias headers header_rows; alias headers= header_rows=
|
28
|
+
|
29
|
+
#
|
30
|
+
# Creates a RubyExcel::Sheet instance
|
31
|
+
#
|
32
|
+
# @param [String] name the name of the Sheet
|
33
|
+
# @param [RubyExcel::Workbook] workbook the Workbook which holds this Sheet
|
34
|
+
#
|
35
|
+
|
36
|
+
def initialize( name, workbook )
|
37
|
+
@workbook = workbook
|
38
|
+
@name = name
|
39
|
+
@header_rows = nil
|
40
|
+
@data = Data.new( self, [[]] )
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Read a value by address
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# sheet['A1']
|
48
|
+
# #=> "Part"
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# sheet['A1:B2']
|
52
|
+
# #=> [["Part", "Ref1"], ["Type1", "QT1"]]
|
53
|
+
#
|
54
|
+
# @param [String] addr the address to access
|
55
|
+
#
|
56
|
+
|
57
|
+
def[]( addr )
|
58
|
+
range( addr ).value
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Write a value by address
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# sheet['A1'] = "Bart"
|
66
|
+
# sheet['A1']
|
67
|
+
# #=> "Bart"
|
68
|
+
#
|
69
|
+
# @param (see #[])
|
70
|
+
# @param [Object] val the value to write into the data
|
71
|
+
#
|
72
|
+
|
73
|
+
def []=( addr, val )
|
74
|
+
range( addr ).value = val
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Add data with the Sheet
|
79
|
+
#
|
80
|
+
# @param [Array<Array>, Hash<Hash>, RubyExcel::Sheet] other the data to add
|
81
|
+
# @return [RubyExcel::Sheet] returns a new Sheet
|
82
|
+
# @note When adding another Sheet it won't import the headers unless this Sheet is empty.
|
83
|
+
#
|
84
|
+
|
85
|
+
def +( other )
|
86
|
+
dup << other
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Subtract data from the Sheet
|
91
|
+
#
|
92
|
+
# @param [Array<Array>, RubyExcel::Sheet] other the data to subtract
|
93
|
+
# @return [RubyExcel::Sheet] returns a new Sheet
|
94
|
+
#
|
95
|
+
|
96
|
+
def -( other )
|
97
|
+
case other
|
98
|
+
when Array ; Workbook.new.load( data.all - other )
|
99
|
+
when Sheet ; Workbook.new.load( data.all - other.data.no_headers )
|
100
|
+
else ; fail ArgumentError, "Unsupported class: #{ other.class }"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Append an object to the Sheet
|
106
|
+
#
|
107
|
+
# @param [Object] other the object to append
|
108
|
+
# @return [self]
|
109
|
+
# @note When adding another Sheet it won't import the headers unless this Sheet is empty.
|
110
|
+
# @note Anything other than an an Array, Hash, Row, Column or Sheet will be appended to the first row
|
111
|
+
#
|
112
|
+
|
113
|
+
def <<( other )
|
114
|
+
data << other
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# @overload advanced_filter!( header, comparison_operator, search_criteria, ... )
|
119
|
+
# Filter on multiple criteria
|
120
|
+
# @param [String] header a header to search under
|
121
|
+
# @param [Symbol] comparison_operator the operator to compare with
|
122
|
+
# @param [Object] search_criteria the value to filter by
|
123
|
+
# @raise [ArgumentError] 'Number of arguments must be a multiple of 3'
|
124
|
+
# @raise [ArgumentError] 'Operator must be a symbol'
|
125
|
+
# @example Filter to 'Part': 'Type1' and 'Type3', with Qty greater than 1
|
126
|
+
# s.advanced_filter!( 'Part', :=~, /Type[13]/, 'Qty', :>, 1 )
|
127
|
+
# @example Filter to 'Part': 'Type1', with 'Ref1' containing 'X'
|
128
|
+
# s.advanced_filter!( 'Part', :==, 'Type1', 'Ref1', :include?, 'X' )
|
129
|
+
#
|
130
|
+
|
131
|
+
def advanced_filter!( *args )
|
132
|
+
data.advanced_filter!( *args ); self
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Access an Element by indices.
|
137
|
+
#
|
138
|
+
# @param [Fixnum] row the row index
|
139
|
+
# @param [Fixnum] col the column index
|
140
|
+
# @return [RubyExcel::Element]
|
141
|
+
# @note Indexing is 1-based like Excel VBA
|
142
|
+
#
|
143
|
+
|
144
|
+
def cell( row, col )
|
145
|
+
Element.new( self, indices_to_address( row, col ) )
|
146
|
+
end
|
147
|
+
alias cells cell
|
148
|
+
|
149
|
+
#
|
150
|
+
# Access a Column (Section) by its reference.
|
151
|
+
#
|
152
|
+
# @param [String, Fixnum] index the Column reference
|
153
|
+
# @return [RubyExcel::Column]
|
154
|
+
# @note Index 'A' and 1 both select the 1st Column
|
155
|
+
#
|
156
|
+
|
157
|
+
def column( index )
|
158
|
+
Column.new( self, col_letter( index ) )
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Access a Column (Section) by its header.
|
163
|
+
#
|
164
|
+
# @param [String] header the Column header
|
165
|
+
# @return [RubyExcel::Column]
|
166
|
+
#
|
167
|
+
|
168
|
+
def column_by_header( header )
|
169
|
+
header.is_a?( Column ) ? header : Column.new( self, data.colref_by_header( header ) )
|
170
|
+
end
|
171
|
+
alias ch column_by_header
|
172
|
+
|
173
|
+
#
|
174
|
+
# Yields each Column to the block
|
175
|
+
#
|
176
|
+
# @param [String, Fixnum] start_column the Column to start looping from
|
177
|
+
# @param [String, Fixnum] end_column the Column to end the loop at
|
178
|
+
# @note Iterates to the last Column in the Sheet unless given a second argument.
|
179
|
+
#
|
180
|
+
|
181
|
+
def columns( start_column = 'A', end_column = data.cols )
|
182
|
+
return to_enum( :columns, start_column, end_column ) unless block_given?
|
183
|
+
( col_letter( start_column )..col_letter( end_column ) ).each { |idx| yield column( idx ) }
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Removes empty Columns and Rows
|
189
|
+
#
|
190
|
+
|
191
|
+
def compact!
|
192
|
+
data.compact!; self
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# Removes Sheet from the parent Workbook
|
197
|
+
#
|
198
|
+
|
199
|
+
def delete
|
200
|
+
workbook.delete self
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Deletes each Row where the block is true
|
205
|
+
#
|
206
|
+
|
207
|
+
def delete_rows_if
|
208
|
+
return to_enum( :delete_rows_if ) unless block_given?
|
209
|
+
rows.reverse_each { |r| r.delete if yield r }; self
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# Deletes each Column where the block is true
|
214
|
+
#
|
215
|
+
|
216
|
+
def delete_columns_if
|
217
|
+
return to_enum( :delete_columns_if ) unless block_given?
|
218
|
+
columns.reverse_each { |c| c.delete if yield c }; self
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# Return a copy of self
|
223
|
+
#
|
224
|
+
# @return [RubyExcel::Sheet]
|
225
|
+
#
|
226
|
+
|
227
|
+
def dup
|
228
|
+
s = Sheet.new( name, workbook )
|
229
|
+
d = data
|
230
|
+
unless d.nil?
|
231
|
+
d = d.dup
|
232
|
+
s.load( d.all, header_rows )
|
233
|
+
d.sheet = s
|
234
|
+
end
|
235
|
+
s
|
236
|
+
end
|
237
|
+
|
238
|
+
#
|
239
|
+
# Check whether the Sheet contains data
|
240
|
+
#
|
241
|
+
# @return [Boolean] if there is any data
|
242
|
+
#
|
243
|
+
|
244
|
+
def empty?
|
245
|
+
data.empty?
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# Removes all Rows (omitting headers) where the block is false
|
250
|
+
#
|
251
|
+
# @param [String] header the header of the Column to pass to the block
|
252
|
+
# @yield [Object] the value at the intersection of Column and Row
|
253
|
+
# @return [self]
|
254
|
+
#
|
255
|
+
|
256
|
+
def filter!( header, &block )
|
257
|
+
data.filter!( header, &block ); self
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# Select and re-order Columns by a list of headers
|
262
|
+
#
|
263
|
+
# @param [Array<String>] headers the ordered list of headers to keep
|
264
|
+
# @note This method can accept either a list of arguments or an Array
|
265
|
+
# @note Invalid headers will be skipped
|
266
|
+
#
|
267
|
+
|
268
|
+
def get_columns!( *headers )
|
269
|
+
data.get_columns!( *headers ); self
|
270
|
+
end
|
271
|
+
alias gc! get_columns!
|
272
|
+
|
273
|
+
# @overload insert_columns( before, number=1 )
|
274
|
+
# Insert blank Columns into the data
|
275
|
+
#
|
276
|
+
# @param [String, Fixnum] before the Column reference to insert before.
|
277
|
+
# @param [Fixnum] number the number of new Columns to insert
|
278
|
+
#
|
279
|
+
|
280
|
+
def insert_columns( *args )
|
281
|
+
data.insert_columns( *args ); self
|
282
|
+
end
|
283
|
+
|
284
|
+
# @overload insert_rows( before, number=1 )
|
285
|
+
# Insert blank Rows into the data
|
286
|
+
#
|
287
|
+
# @param [Fixnum] before the Row index to insert before.
|
288
|
+
# @param [Fixnum] number the number of new Rows to insert
|
289
|
+
#
|
290
|
+
|
291
|
+
def insert_rows( *args )
|
292
|
+
data.insert_rows( *args ); self
|
293
|
+
end
|
294
|
+
|
295
|
+
#
|
296
|
+
# View the object for debugging
|
297
|
+
#
|
298
|
+
|
299
|
+
def inspect
|
300
|
+
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ name }"
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Populate the Sheet with data (overwrite)
|
305
|
+
#
|
306
|
+
# @param [Array<Array>, Hash<Hash>] input_data the data to fill the Sheet with
|
307
|
+
# @param header_rows [Fixnum] the number of Rows to be treated as headers
|
308
|
+
#
|
309
|
+
|
310
|
+
def load( input_data, header_rows=1 )
|
311
|
+
input_data = _convert_hash(input_data) if input_data.is_a?(Hash)
|
312
|
+
input_data.is_a?(Array) or fail ArgumentError, 'Input must be an Array or Hash'
|
313
|
+
@header_rows = header_rows
|
314
|
+
@data = Data.new( self, input_data ); self
|
315
|
+
end
|
316
|
+
|
317
|
+
#
|
318
|
+
# Find the row number by looking up a value in a Column
|
319
|
+
#
|
320
|
+
# @param [String] header the header of the Column to pass to the block
|
321
|
+
# @yield yields each value in the Column to the block
|
322
|
+
# @return [Fixnum, nil] the row number of the first match or nil if nothing is found
|
323
|
+
#
|
324
|
+
|
325
|
+
def match( header, &block )
|
326
|
+
row_id( column_by_header( header ).find( &block ) )
|
327
|
+
end
|
328
|
+
|
329
|
+
#
|
330
|
+
# The highest currently used row number
|
331
|
+
#
|
332
|
+
|
333
|
+
def maxrow
|
334
|
+
data.rows
|
335
|
+
end
|
336
|
+
|
337
|
+
#
|
338
|
+
# The highest currently used column number
|
339
|
+
#
|
340
|
+
|
341
|
+
def maxcol
|
342
|
+
data.cols
|
343
|
+
end
|
344
|
+
alias maxcolumn maxcol
|
345
|
+
|
346
|
+
#
|
347
|
+
# Allow shorthand range references and non-bang versions of bang methods.
|
348
|
+
#
|
349
|
+
|
350
|
+
def method_missing(m, *args, &block)
|
351
|
+
method_name = m.to_s
|
352
|
+
|
353
|
+
if method_name[-1] != '!' && respond_to?( method_name + '!' )
|
354
|
+
|
355
|
+
dup.send( method_name + '!', *args, &block )
|
356
|
+
|
357
|
+
elsif method_name =~ /\A[A-Z]{1,3}\d+=?\z/i
|
358
|
+
|
359
|
+
method_name.upcase!
|
360
|
+
if method_name[-1] == '='
|
361
|
+
range( method_name.chop ).value = ( args.length == 1 ? args.first : args )
|
362
|
+
else
|
363
|
+
range( method_name ).value
|
364
|
+
end
|
365
|
+
|
366
|
+
else
|
367
|
+
super
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
#
|
372
|
+
# Allow for certain method_missing calls
|
373
|
+
#
|
374
|
+
|
375
|
+
def respond_to?(m)
|
376
|
+
|
377
|
+
if m[-1] != '!' && respond_to?( m.to_s + '!' )
|
378
|
+
true
|
379
|
+
elsif m.to_s.upcase.strip =~ /\A[A-Z]{1,3}\d+=?\z/
|
380
|
+
true
|
381
|
+
else
|
382
|
+
super
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# Split the Sheet into two Sheets by evaluating each value in a column
|
389
|
+
#
|
390
|
+
# @param [String] header the header of the Column which contains the yield value
|
391
|
+
# @yield [value] yields the value of each row under the given header
|
392
|
+
# @return [Array<RubyExcel::Sheet, RubyExcel::Sheet>] Two Sheets: true and false. Headers included.
|
393
|
+
#
|
394
|
+
|
395
|
+
def partition( header, &block )
|
396
|
+
data.partition( header, &block ).map { |d| dup.load( d ) }
|
397
|
+
end
|
398
|
+
|
399
|
+
#
|
400
|
+
# Access an Element by address.
|
401
|
+
#
|
402
|
+
# @param [String, Element] first_cell the first Cell or Address in the Range
|
403
|
+
# @param [String, Element] last_cell the last Cell or Address in the Range
|
404
|
+
# @return [RubyExcel::Element]
|
405
|
+
# @note These are all valid arguments:
|
406
|
+
# ('A1')
|
407
|
+
# ('A1:B2')
|
408
|
+
# ('A:A')
|
409
|
+
# ('1:1')
|
410
|
+
# ('A1', 'B2')
|
411
|
+
# (cell1)
|
412
|
+
# (cell1, cell2)
|
413
|
+
#
|
414
|
+
|
415
|
+
def range( first_cell, last_cell=nil )
|
416
|
+
Element.new( self, to_range_address( first_cell, last_cell ) )
|
417
|
+
end
|
418
|
+
|
419
|
+
#
|
420
|
+
# Reverse the Sheet Columns
|
421
|
+
#
|
422
|
+
|
423
|
+
def reverse_columns!
|
424
|
+
data.reverse_columns!; self
|
425
|
+
end
|
426
|
+
|
427
|
+
#
|
428
|
+
# Reverse the Sheet Rows (without affecting the headers)
|
429
|
+
#
|
430
|
+
|
431
|
+
def reverse_rows!
|
432
|
+
data.reverse_rows!; self
|
433
|
+
end
|
434
|
+
|
435
|
+
#
|
436
|
+
# Create a Row from an index
|
437
|
+
#
|
438
|
+
# @param [Fixnum] index the Row index
|
439
|
+
# @return [RubyExcel::Row]
|
440
|
+
#
|
441
|
+
|
442
|
+
def row( index )
|
443
|
+
Row.new( self, index )
|
444
|
+
end
|
445
|
+
|
446
|
+
#
|
447
|
+
# Yields each Row to the block
|
448
|
+
#
|
449
|
+
# @param [Fixnum] start_row the Row to start looping from
|
450
|
+
# @param [Fixnum] end_row the Row to end the loop at
|
451
|
+
# @note Iterates to the last Row in the Sheet unless given a second argument.
|
452
|
+
#
|
453
|
+
|
454
|
+
def rows( start_row = 1, end_row = data.rows )
|
455
|
+
return to_enum(:rows, start_row, end_row) unless block_given?
|
456
|
+
( start_row..end_row ).each { |idx| yield row( idx ) }; self
|
457
|
+
end
|
458
|
+
alias each rows
|
459
|
+
|
460
|
+
#
|
461
|
+
# Sort the data according to a block (avoiding headers)
|
462
|
+
#
|
463
|
+
|
464
|
+
def sort!( &block )
|
465
|
+
data.sort!( &block ); self
|
466
|
+
end
|
467
|
+
|
468
|
+
#
|
469
|
+
# Sort the data by a column, selected by header
|
470
|
+
#
|
471
|
+
# @param [String] header the header to sort the Sheet by
|
472
|
+
#
|
473
|
+
|
474
|
+
def sort_by!( header )
|
475
|
+
raise ArgumentError, 'Sheet#sort_by! does not support blocks.' if block_given?
|
476
|
+
idx = data.index_by_header( header ) - 1
|
477
|
+
sort_method = lambda { |array| array[idx] }
|
478
|
+
data.sort_by!( &sort_method )
|
479
|
+
self
|
480
|
+
end
|
481
|
+
|
482
|
+
#
|
483
|
+
# Break the Sheet into a Workbook with multiple Sheets, split by the values under a header.
|
484
|
+
#
|
485
|
+
# @param [String] header the header to split by
|
486
|
+
# @return [RubyExcel::Workbook] a new workbook containing the split Sheets (each with headers)
|
487
|
+
#
|
488
|
+
|
489
|
+
def split( header )
|
490
|
+
wb = Workbook.new
|
491
|
+
ch( header ).each_wh.to_a.uniq.each { |name| wb.add( name ).load( data.headers ) }
|
492
|
+
rows( header_rows+1 ) do |row|
|
493
|
+
wb.sheets( row.val( header ) ) << row
|
494
|
+
end
|
495
|
+
wb
|
496
|
+
end
|
497
|
+
|
498
|
+
#
|
499
|
+
# Sum the values in a Column by searching another Column
|
500
|
+
#
|
501
|
+
# @param [String] find_header the header of the Column to yield to the block
|
502
|
+
# @param [String] sum_header the header of the Column to sum
|
503
|
+
# @yield yields the find_header column values to the block
|
504
|
+
#
|
505
|
+
|
506
|
+
def sumif( find_header, sum_header )
|
507
|
+
return to_enum( :sumif ) unless block_given?
|
508
|
+
find_col, sum_col = ch( find_header ), ch( sum_header )
|
509
|
+
find_col.each_cell.inject(0) { |sum,ce| yield( ce.value ) && ce.row > header_rows ? sum + sum_col[ ce.row ] : sum }
|
510
|
+
end
|
511
|
+
|
512
|
+
#
|
513
|
+
# Summarise the values of a Column into a Hash
|
514
|
+
#
|
515
|
+
# @param [String] header the header of the Column to summarise
|
516
|
+
# @return [Hash]
|
517
|
+
#
|
518
|
+
|
519
|
+
def summarise( header )
|
520
|
+
ch( header ).summarise
|
521
|
+
end
|
522
|
+
|
523
|
+
#
|
524
|
+
# The Sheet as a 2D Array
|
525
|
+
#
|
526
|
+
|
527
|
+
def to_a
|
528
|
+
data.all
|
529
|
+
end
|
530
|
+
|
531
|
+
#
|
532
|
+
# The Sheet as a WIN32OLE Excel Workbook
|
533
|
+
# @note This requires Windows and MS Excel
|
534
|
+
#
|
535
|
+
|
536
|
+
def to_excel
|
537
|
+
workbook.dup.clear_all.add( self.dup ).workbook.to_excel
|
538
|
+
end
|
539
|
+
|
540
|
+
#
|
541
|
+
# The Sheet as a String containing an HTML Table
|
542
|
+
#
|
543
|
+
|
544
|
+
def to_html
|
545
|
+
"<table>\n" + data.map { |row| '<tr>' + row.map { |v| '<td>' + CGI.escapeHTML(v.to_s) }.join() + "\n" }.join() + '</table>'
|
546
|
+
end
|
547
|
+
|
548
|
+
#
|
549
|
+
# The Sheet as a Tab Seperated Value String
|
550
|
+
#
|
551
|
+
|
552
|
+
def to_s
|
553
|
+
data.map { |ar| ar.map { |v| v.to_s.gsub(/\t|\n/,' ') }.join "\t" }.join( $/ )
|
554
|
+
end
|
555
|
+
|
556
|
+
#
|
557
|
+
# Remove any Rows with duplicate values within a Column
|
558
|
+
#
|
559
|
+
# @param [String] header the header of the Column to check for duplicates
|
560
|
+
#
|
561
|
+
|
562
|
+
def uniq!( header )
|
563
|
+
data.uniq!( header ); self
|
564
|
+
end
|
565
|
+
alias unique! uniq!
|
566
|
+
|
567
|
+
#
|
568
|
+
# Find a value within a Column by searching another Column
|
569
|
+
#
|
570
|
+
# @param [String] find_header the header of the Column to search
|
571
|
+
# @param [String] return_header the header of the return value Column
|
572
|
+
# @yield the first matching value
|
573
|
+
#
|
574
|
+
|
575
|
+
def vlookup( find_header, return_header, &block )
|
576
|
+
find_col, return_col = ch( find_header ), ch( return_header )
|
577
|
+
return_col[ row_id( find_col.find( &block ) ) ] rescue nil
|
578
|
+
end
|
579
|
+
|
580
|
+
end # Sheet
|
581
|
+
|
582
|
+
end # RubyExcel
|
data/lib/rubyexcel.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'rubyexcel/rubyexcel_components.rb'
|
2
|
-
require_relative 'rubyexcel/excel_tools.rb'
|
3
2
|
require 'cgi'
|
4
3
|
|
5
4
|
#
|
@@ -185,612 +184,5 @@ module RubyExcel
|
|
185
184
|
end
|
186
185
|
|
187
186
|
end # Workbook
|
188
|
-
|
189
|
-
#
|
190
|
-
# The front-end class for data manipulation and output.
|
191
|
-
#
|
192
|
-
|
193
|
-
class Sheet
|
194
|
-
include Address
|
195
|
-
include Enumerable
|
196
|
-
|
197
|
-
# The Data underlying the Sheet
|
198
|
-
attr_reader :data
|
199
|
-
|
200
|
-
# The name of the Sheet
|
201
|
-
attr_accessor :name
|
202
|
-
|
203
|
-
# The number of rows treated as headers
|
204
|
-
attr_accessor :header_rows
|
205
|
-
|
206
|
-
# The Workbook parent of this Sheet
|
207
|
-
attr_accessor :workbook
|
208
|
-
|
209
|
-
alias parent workbook; alias parent= workbook=
|
210
|
-
alias headers header_rows; alias headers= header_rows=
|
211
|
-
|
212
|
-
#
|
213
|
-
# Creates a RubyExcel::Sheet instance
|
214
|
-
#
|
215
|
-
# @param [String] name the name of the Sheet
|
216
|
-
# @param [RubyExcel::Workbook] workbook the Workbook which holds this Sheet
|
217
|
-
#
|
218
|
-
|
219
|
-
def initialize( name, workbook )
|
220
|
-
@workbook = workbook
|
221
|
-
@name = name
|
222
|
-
@header_rows = nil
|
223
|
-
@data = Data.new( self, [[]] )
|
224
|
-
end
|
225
|
-
|
226
|
-
#
|
227
|
-
# Read a value by address
|
228
|
-
#
|
229
|
-
# @example
|
230
|
-
# sheet['A1']
|
231
|
-
# #=> "Part"
|
232
|
-
#
|
233
|
-
# @example
|
234
|
-
# sheet['A1:B2']
|
235
|
-
# #=> [["Part", "Ref1"], ["Type1", "QT1"]]
|
236
|
-
#
|
237
|
-
# @param [String] addr the address to access
|
238
|
-
#
|
239
|
-
|
240
|
-
def[]( addr )
|
241
|
-
range( addr ).value
|
242
|
-
end
|
243
|
-
|
244
|
-
#
|
245
|
-
# Write a value by address
|
246
|
-
#
|
247
|
-
# @example
|
248
|
-
# sheet['A1'] = "Bart"
|
249
|
-
# sheet['A1']
|
250
|
-
# #=> "Bart"
|
251
|
-
#
|
252
|
-
# @param (see #[])
|
253
|
-
# @param [Object] val the value to write into the data
|
254
|
-
#
|
255
|
-
|
256
|
-
def []=( addr, val )
|
257
|
-
range( addr ).value = val
|
258
|
-
end
|
259
|
-
|
260
|
-
#
|
261
|
-
# Add data with the Sheet
|
262
|
-
#
|
263
|
-
# @param [Array<Array>, Hash<Hash>, RubyExcel::Sheet] other the data to add
|
264
|
-
# @return [RubyExcel::Sheet] returns a new Sheet
|
265
|
-
# @note When adding another Sheet it won't import the headers unless this Sheet is empty.
|
266
|
-
#
|
267
|
-
|
268
|
-
def +( other )
|
269
|
-
dup << other
|
270
|
-
end
|
271
|
-
|
272
|
-
#
|
273
|
-
# Subtract data from the Sheet
|
274
|
-
#
|
275
|
-
# @param [Array<Array>, RubyExcel::Sheet] other the data to subtract
|
276
|
-
# @return [RubyExcel::Sheet] returns a new Sheet
|
277
|
-
#
|
278
|
-
|
279
|
-
def -( other )
|
280
|
-
case other
|
281
|
-
when Array ; Workbook.new.load( data.all - other )
|
282
|
-
when Sheet ; Workbook.new.load( data.all - other.data.no_headers )
|
283
|
-
else ; fail ArgumentError, "Unsupported class: #{ other.class }"
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
#
|
288
|
-
# Append an object to the Sheet
|
289
|
-
#
|
290
|
-
# @param [Object] other the object to append
|
291
|
-
# @return [self]
|
292
|
-
# @note When adding another Sheet it won't import the headers unless this Sheet is empty.
|
293
|
-
# @note Anything other than an an Array, Hash, Row, Column or Sheet will be appended to the first row
|
294
|
-
#
|
295
|
-
|
296
|
-
def <<( other )
|
297
|
-
data << other
|
298
|
-
self
|
299
|
-
end
|
300
|
-
|
301
|
-
# {Sheet#advanced_filter!}
|
302
|
-
|
303
|
-
def advanced_filter( *args )
|
304
|
-
dup.advanced_filter!( *args )
|
305
|
-
end
|
306
|
-
|
307
|
-
# @overload advanced_filter!( header, comparison_operator, search_criteria, ... )
|
308
|
-
# Filter on multiple criteria
|
309
|
-
# @example Filter to 'Part': 'Type1' and 'Type3', with Qty greater than 1
|
310
|
-
# s.advanced_filter!( 'Part', :=~, /Type[13]/, 'Qty', :>, 1 )
|
311
|
-
# @example Filter to 'Part': 'Type1', with 'Ref1' containing 'X'
|
312
|
-
# s.advanced_filter!( 'Part', :==, 'Type1', 'Ref1', :include?, 'X' )
|
313
|
-
#
|
314
|
-
# @param [String] header a header to search under
|
315
|
-
# @param [Symbol] comparison_operator the operator to compare with
|
316
|
-
# @param [Object] search_criteria the value to filter by
|
317
|
-
# @raise [ArgumentError] 'Number of arguments must be a multiple of 3'
|
318
|
-
# @raise [ArgumentError] 'Operator must be a symbol'
|
319
|
-
#
|
320
|
-
|
321
|
-
def advanced_filter!( *args )
|
322
|
-
data.advanced_filter!( *args ); self
|
323
|
-
end
|
324
|
-
|
325
|
-
#
|
326
|
-
# Access an Element by indices.
|
327
|
-
#
|
328
|
-
# @param [Fixnum] row the row index
|
329
|
-
# @param [Fixnum] col the column index
|
330
|
-
# @return [RubyExcel::Element]
|
331
|
-
# @note Indexing is 1-based like Excel VBA
|
332
|
-
#
|
333
|
-
|
334
|
-
def cell( row, col )
|
335
|
-
Element.new( self, indices_to_address( row, col ) )
|
336
|
-
end
|
337
|
-
alias cells cell
|
338
|
-
|
339
|
-
#
|
340
|
-
# Access a Column (Section) by its reference.
|
341
|
-
#
|
342
|
-
# @param [String, Fixnum] index the Column reference
|
343
|
-
# @return [RubyExcel::Column]
|
344
|
-
# @note Index 'A' and 1 both select the 1st Column
|
345
|
-
#
|
346
|
-
|
347
|
-
def column( index )
|
348
|
-
Column.new( self, col_letter( index ) )
|
349
|
-
end
|
350
|
-
|
351
|
-
#
|
352
|
-
# Access a Column (Section) by its header.
|
353
|
-
#
|
354
|
-
# @param [String] header the Column header
|
355
|
-
# @return [RubyExcel::Column]
|
356
|
-
#
|
357
|
-
|
358
|
-
def column_by_header( header )
|
359
|
-
header.is_a?( Column ) ? header : Column.new( self, data.colref_by_header( header ) )
|
360
|
-
end
|
361
|
-
alias ch column_by_header
|
362
|
-
|
363
|
-
#
|
364
|
-
# Yields each Column to the block
|
365
|
-
#
|
366
|
-
# @param [String, Fixnum] start_column the Column to start looping from
|
367
|
-
# @param [String, Fixnum] end_column the Column to end the loop at
|
368
|
-
# @note Iterates to the last Column in the Sheet unless given a second argument.
|
369
|
-
#
|
370
|
-
|
371
|
-
def columns( start_column = 'A', end_column = data.cols )
|
372
|
-
return to_enum( :columns, start_column, end_column ) unless block_given?
|
373
|
-
( col_letter( start_column )..col_letter( end_column ) ).each { |idx| yield column( idx ) }
|
374
|
-
self
|
375
|
-
end
|
376
|
-
|
377
|
-
# {Sheet#compact!}
|
378
|
-
|
379
|
-
def compact
|
380
|
-
dup.compact!
|
381
|
-
end
|
382
|
-
|
383
|
-
#
|
384
|
-
# Removes empty Columns and Rows
|
385
|
-
#
|
386
|
-
|
387
|
-
def compact!
|
388
|
-
data.compact!; self
|
389
|
-
end
|
390
|
-
|
391
|
-
#
|
392
|
-
# Removes Sheet from the parent Workbook
|
393
|
-
#
|
394
|
-
|
395
|
-
def delete
|
396
|
-
workbook.delete self
|
397
|
-
end
|
398
|
-
|
399
|
-
#
|
400
|
-
# Deletes each Row where the block is true
|
401
|
-
#
|
402
|
-
|
403
|
-
def delete_rows_if
|
404
|
-
return to_enum( :delete_rows_if ) unless block_given?
|
405
|
-
rows.reverse_each { |r| r.delete if yield r }; self
|
406
|
-
end
|
407
|
-
|
408
|
-
#
|
409
|
-
# Deletes each Column where the block is true
|
410
|
-
#
|
411
|
-
|
412
|
-
def delete_columns_if
|
413
|
-
return to_enum( :delete_columns_if ) unless block_given?
|
414
|
-
columns.reverse_each { |c| c.delete if yield c }; self
|
415
|
-
end
|
416
|
-
|
417
|
-
#
|
418
|
-
# Return a copy of self
|
419
|
-
#
|
420
|
-
# @return [RubyExcel::Sheet]
|
421
|
-
#
|
422
|
-
|
423
|
-
def dup
|
424
|
-
s = Sheet.new( name, workbook )
|
425
|
-
d = data
|
426
|
-
unless d.nil?
|
427
|
-
d = d.dup
|
428
|
-
s.load( d.all, header_rows )
|
429
|
-
d.sheet = s
|
430
|
-
end
|
431
|
-
s
|
432
|
-
end
|
433
|
-
|
434
|
-
#
|
435
|
-
# Check whether the Sheet contains data
|
436
|
-
#
|
437
|
-
# @return [Boolean] if there is any data
|
438
|
-
#
|
439
|
-
|
440
|
-
def empty?
|
441
|
-
data.empty?
|
442
|
-
end
|
443
|
-
|
444
|
-
# {Sheet#filter!}
|
445
|
-
|
446
|
-
def filter( header, &block )
|
447
|
-
dup.filter!( header, &block )
|
448
|
-
end
|
449
|
-
|
450
|
-
#
|
451
|
-
# Removes all Rows (omitting headers) where the block is false
|
452
|
-
#
|
453
|
-
# @param [String] header the header of the Column to pass to the block
|
454
|
-
# @yield [Object] the value at the intersection of Column and Row
|
455
|
-
# @return [self]
|
456
|
-
#
|
457
|
-
|
458
|
-
def filter!( header, &block )
|
459
|
-
data.filter!( header, &block ); self
|
460
|
-
end
|
461
|
-
|
462
|
-
# {Sheet#get_columns!}
|
463
|
-
|
464
|
-
def get_columns( *headers )
|
465
|
-
dup.get_columns!( *headers )
|
466
|
-
end
|
467
|
-
alias gc get_columns
|
468
|
-
|
469
|
-
#
|
470
|
-
# Select and re-order Columns by a list of headers
|
471
|
-
#
|
472
|
-
# @param [Array<String>] headers the ordered list of headers to keep
|
473
|
-
# @note This method can accept either a list of arguments or an Array
|
474
|
-
# @note Invalid headers will be skipped
|
475
|
-
#
|
476
|
-
|
477
|
-
def get_columns!( *headers )
|
478
|
-
data.get_columns!( *headers ); self
|
479
|
-
end
|
480
|
-
alias gc! get_columns!
|
481
|
-
|
482
|
-
# @overload insert_columns( before, number=1 )
|
483
|
-
# Insert blank Columns into the data
|
484
|
-
#
|
485
|
-
# @param [String, Fixnum] before the Column reference to insert before.
|
486
|
-
# @param [Fixnum] number the number of new Columns to insert
|
487
|
-
#
|
488
|
-
|
489
|
-
def insert_columns( *args )
|
490
|
-
data.insert_columns( *args ); self
|
491
|
-
end
|
492
|
-
|
493
|
-
# @overload insert_rows( before, number=1 )
|
494
|
-
# Insert blank Rows into the data
|
495
|
-
#
|
496
|
-
# @param [Fixnum] before the Row index to insert before.
|
497
|
-
# @param [Fixnum] number the number of new Rows to insert
|
498
|
-
#
|
499
|
-
|
500
|
-
def insert_rows( *args )
|
501
|
-
data.insert_rows( *args ); self
|
502
|
-
end
|
503
|
-
|
504
|
-
#
|
505
|
-
# View the object for debugging
|
506
|
-
#
|
507
|
-
|
508
|
-
def inspect
|
509
|
-
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ name }"
|
510
|
-
end
|
511
|
-
|
512
|
-
#
|
513
|
-
# Populate the Sheet with data (overwrite)
|
514
|
-
#
|
515
|
-
# @param [Array<Array>, Hash<Hash>] input_data the data to fill the Sheet with
|
516
|
-
# @param header_rows [Fixnum] the number of Rows to be treated as headers
|
517
|
-
#
|
518
|
-
|
519
|
-
def load( input_data, header_rows=1 )
|
520
|
-
input_data = _convert_hash(input_data) if input_data.is_a?(Hash)
|
521
|
-
input_data.is_a?(Array) or fail ArgumentError, 'Input must be an Array or Hash'
|
522
|
-
@header_rows = header_rows
|
523
|
-
@data = Data.new( self, input_data ); self
|
524
|
-
end
|
525
|
-
|
526
|
-
#
|
527
|
-
# Find the row number by looking up a value in a Column
|
528
|
-
#
|
529
|
-
# @param [String] header the header of the Column to pass to the block
|
530
|
-
# @yield yields each value in the Column to the block
|
531
|
-
# @return [Fixnum, nil] the row number of the first match or nil if nothing is found
|
532
|
-
#
|
533
|
-
|
534
|
-
def match( header, &block )
|
535
|
-
row_id( column_by_header( header ).find( &block ) )
|
536
|
-
end
|
537
|
-
|
538
|
-
#
|
539
|
-
# The highest currently used row number
|
540
|
-
#
|
541
|
-
|
542
|
-
def maxrow
|
543
|
-
data.rows
|
544
|
-
end
|
545
|
-
|
546
|
-
#
|
547
|
-
# The highest currently used column number
|
548
|
-
#
|
549
|
-
|
550
|
-
def maxcol
|
551
|
-
data.cols
|
552
|
-
end
|
553
|
-
alias maxcolumn maxcol
|
554
|
-
|
555
|
-
#
|
556
|
-
# Allow shorthand range references
|
557
|
-
#
|
558
|
-
|
559
|
-
def method_missing(m, *args, &block)
|
560
|
-
method_name = m.to_s.upcase.strip
|
561
|
-
if method_name =~ /\A[A-Z]{1,3}\d+=?\z/
|
562
|
-
if method_name[-1] == '='
|
563
|
-
range( method_name.chop ).value = ( args.length == 1 ? args.first : args )
|
564
|
-
else
|
565
|
-
range( method_name ).value
|
566
|
-
end
|
567
|
-
else
|
568
|
-
super
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
#
|
573
|
-
# Allow for certain method_missing calls
|
574
|
-
#
|
575
|
-
|
576
|
-
def respond_to?(meth)
|
577
|
-
if meth.to_s.upcase.strip =~ /\A[A-Z]{1,3}\d+=?\z/
|
578
|
-
true
|
579
|
-
else
|
580
|
-
super
|
581
|
-
end
|
582
|
-
end
|
583
|
-
|
584
|
-
#
|
585
|
-
# Split the Sheet into two Sheets by evaluating each value in a column
|
586
|
-
#
|
587
|
-
# @param [String] header the header of the Column which contains the yield value
|
588
|
-
# @yield [value] yields the value of each row under the given header
|
589
|
-
# @return [Array<RubyExcel::Sheet, RubyExcel::Sheet>] Two Sheets: true and false. Headers included.
|
590
|
-
#
|
591
|
-
|
592
|
-
def partition( header, &block )
|
593
|
-
data.partition( header, &block ).map { |d| dup.load( d ) }
|
594
|
-
end
|
595
|
-
|
596
|
-
#
|
597
|
-
# Access an Element by address.
|
598
|
-
#
|
599
|
-
# @param [String, Element] first_cell the first Cell or Address in the Range
|
600
|
-
# @param [String, Element] last_cell the last Cell or Address in the Range
|
601
|
-
# @return [RubyExcel::Element]
|
602
|
-
# @note These are all valid arguments:
|
603
|
-
# ('A1')
|
604
|
-
# ('A1:B2')
|
605
|
-
# ('A:A')
|
606
|
-
# ('1:1')
|
607
|
-
# ('A1', 'B2')
|
608
|
-
# (cell1)
|
609
|
-
# (cell1, cell2)
|
610
|
-
#
|
611
|
-
|
612
|
-
def range( first_cell, last_cell=nil )
|
613
|
-
Element.new( self, to_range_address( first_cell, last_cell ) )
|
614
|
-
end
|
615
|
-
|
616
|
-
#
|
617
|
-
# Reverse the Sheet Columns
|
618
|
-
#
|
619
|
-
|
620
|
-
def reverse_columns!
|
621
|
-
data.reverse_columns!; self
|
622
|
-
end
|
623
|
-
|
624
|
-
#
|
625
|
-
# Reverse the Sheet Rows (without affecting the headers)
|
626
|
-
#
|
627
|
-
|
628
|
-
def reverse_rows!
|
629
|
-
data.reverse_rows!; self
|
630
|
-
end
|
631
|
-
|
632
|
-
#
|
633
|
-
# Create a Row from an index
|
634
|
-
#
|
635
|
-
# @param [Fixnum] index the Row index
|
636
|
-
# @return [RubyExcel::Row]
|
637
|
-
#
|
638
187
|
|
639
|
-
def row( index )
|
640
|
-
Row.new( self, index )
|
641
|
-
end
|
642
|
-
|
643
|
-
#
|
644
|
-
# Yields each Row to the block
|
645
|
-
#
|
646
|
-
# @param [Fixnum] start_row the Row to start looping from
|
647
|
-
# @param [Fixnum] end_row the Row to end the loop at
|
648
|
-
# @note Iterates to the last Row in the Sheet unless given a second argument.
|
649
|
-
#
|
650
|
-
|
651
|
-
def rows( start_row = 1, end_row = data.rows )
|
652
|
-
return to_enum(:rows, start_row, end_row) unless block_given?
|
653
|
-
( start_row..end_row ).each { |idx| yield row( idx ) }; self
|
654
|
-
end
|
655
|
-
alias each rows
|
656
|
-
|
657
|
-
# {Sheet#sort!}
|
658
|
-
|
659
|
-
def sort( &block )
|
660
|
-
dup.sort!( &block )
|
661
|
-
end
|
662
|
-
|
663
|
-
#
|
664
|
-
# Sort the data according to a block (avoiding headers)
|
665
|
-
#
|
666
|
-
|
667
|
-
def sort!( &block )
|
668
|
-
data.sort!( &block ); self
|
669
|
-
end
|
670
|
-
|
671
|
-
# {Sheet#sort_by!}
|
672
|
-
|
673
|
-
def sort_by( header )
|
674
|
-
dup.sort_by!( header )
|
675
|
-
end
|
676
|
-
|
677
|
-
#
|
678
|
-
# Sort the data by a column, selected by header
|
679
|
-
#
|
680
|
-
# @param [String] header the header to sort the Sheet by
|
681
|
-
#
|
682
|
-
|
683
|
-
def sort_by!( header )
|
684
|
-
idx = data.index_by_header( header ) - 1
|
685
|
-
sort_method = lambda { |array| array[idx] }
|
686
|
-
data.sort_by!( &sort_method )
|
687
|
-
self
|
688
|
-
end
|
689
|
-
|
690
|
-
#
|
691
|
-
# Break the Sheet into a Workbook with multiple Sheets, split by the values under a header.
|
692
|
-
#
|
693
|
-
# @param [String] header the header to split by
|
694
|
-
# @return [RubyExcel::Workbook] a new workbook containing the split Sheets (each with headers)
|
695
|
-
#
|
696
|
-
|
697
|
-
def split( header )
|
698
|
-
wb = Workbook.new
|
699
|
-
ch( header ).each_wh.to_a.uniq.each { |name| wb.add( name ).load( data.headers ) }
|
700
|
-
rows( header_rows+1 ) do |row|
|
701
|
-
wb.sheets( row.val( header ) ) << row
|
702
|
-
end
|
703
|
-
wb
|
704
|
-
end
|
705
|
-
|
706
|
-
#
|
707
|
-
# Sum the values in a Column by searching another Column
|
708
|
-
#
|
709
|
-
# @param [String] find_header the header of the Column to yield to the block
|
710
|
-
# @param [String] sum_header the header of the Column to sum
|
711
|
-
# @yield yields the find_header column values to the block
|
712
|
-
#
|
713
|
-
|
714
|
-
def sumif( find_header, sum_header )
|
715
|
-
return to_enum( :sumif ) unless block_given?
|
716
|
-
find_col, sum_col = ch( find_header ), ch( sum_header )
|
717
|
-
find_col.each_cell.inject(0) { |sum,ce| yield( ce.value ) && ce.row > header_rows ? sum + sum_col[ ce.row ] : sum }
|
718
|
-
end
|
719
|
-
|
720
|
-
#
|
721
|
-
# Summarise the values of a Column into a Hash
|
722
|
-
#
|
723
|
-
# @param [String] header the header of the Column to summarise
|
724
|
-
# @return [Hash]
|
725
|
-
#
|
726
|
-
|
727
|
-
def summarise( header )
|
728
|
-
ch( header ).summarise
|
729
|
-
end
|
730
|
-
|
731
|
-
#
|
732
|
-
# The Sheet as a 2D Array
|
733
|
-
#
|
734
|
-
|
735
|
-
def to_a
|
736
|
-
data.all
|
737
|
-
end
|
738
|
-
|
739
|
-
#
|
740
|
-
# The Sheet as a WIN32OLE Excel Workbook
|
741
|
-
# @note This requires Windows and MS Excel
|
742
|
-
#
|
743
|
-
|
744
|
-
def to_excel
|
745
|
-
workbook.dup.clear_all.add( self.dup ).workbook.to_excel
|
746
|
-
end
|
747
|
-
|
748
|
-
#
|
749
|
-
# The Sheet as a String containing an HTML Table
|
750
|
-
#
|
751
|
-
|
752
|
-
def to_html
|
753
|
-
"<table>\n" + data.map { |row| '<tr>' + row.map { |v| '<td>' + CGI.escapeHTML(v.to_s) }.join() + "\n" }.join() + '</table>'
|
754
|
-
end
|
755
|
-
|
756
|
-
#
|
757
|
-
# The Sheet as a Tab Seperated Value String
|
758
|
-
#
|
759
|
-
|
760
|
-
def to_s
|
761
|
-
data.map { |ar| ar.map { |v| v.to_s.gsub(/\t|\n/,' ') }.join "\t" }.join( $/ )
|
762
|
-
end
|
763
|
-
|
764
|
-
# {Sheet#uniq!}
|
765
|
-
|
766
|
-
def uniq( header )
|
767
|
-
dup.uniq!( header )
|
768
|
-
end
|
769
|
-
|
770
|
-
#
|
771
|
-
# Remove any Rows with duplicate values within a Column
|
772
|
-
#
|
773
|
-
# @param [String] header the header of the Column to check for duplicates
|
774
|
-
#
|
775
|
-
|
776
|
-
def uniq!( header )
|
777
|
-
data.uniq!( header ); self
|
778
|
-
end
|
779
|
-
alias unique! uniq!
|
780
|
-
|
781
|
-
#
|
782
|
-
# Find a value within a Column by searching another Column
|
783
|
-
#
|
784
|
-
# @param [String] find_header the header of the Column to search
|
785
|
-
# @param [String] return_header the header of the return value Column
|
786
|
-
# @yield the first matching value
|
787
|
-
#
|
788
|
-
|
789
|
-
def vlookup( find_header, return_header, &block )
|
790
|
-
find_col, return_col = ch( find_header ), ch( return_header )
|
791
|
-
return_col[ row_id( find_col.find( &block ) ) ] rescue nil
|
792
|
-
end
|
793
|
-
|
794
|
-
end # Sheet
|
795
|
-
|
796
188
|
end # RubyExcel
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyexcel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-07 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A tabular data structure in Ruby, with header-based helper methods and
|
15
15
|
some of Excel's API style.
|
@@ -24,6 +24,7 @@ files:
|
|
24
24
|
- lib/rubyexcel/excel_tools.rb
|
25
25
|
- lib/rubyexcel/rubyexcel_components.rb
|
26
26
|
- lib/rubyexcel/section.rb
|
27
|
+
- lib/rubyexcel/sheet.rb
|
27
28
|
- lib/rubyexcel.rb
|
28
29
|
- lib/README.md
|
29
30
|
homepage: https://github.com/VirtuosoJoel
|