rubyexcel 0.3.9 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 646ece59585c7f66430bcf3b9d94d0021112c0ef
4
- data.tar.gz: 652e6a16f2b6f3e23b105786189bee8e71e97f8a
3
+ metadata.gz: aa9b176920ae6fbcf1b1f89dd5f7afe493b3c5fb
4
+ data.tar.gz: 933f945e2fca3b40124e86b0e4c9b489b78c2a66
5
5
  SHA512:
6
- metadata.gz: c3afbc8d785d392b6307e2a8b545ea85dc1dbbf2973c4ef3f6e796cf3ddcacc3fa9feea36e21745650f6a0f879bea348d3e71fe383371fc866d8c7318d6ba952
7
- data.tar.gz: ea43b28ca7f3498a31d318e12e906af3315789f4089692b8b425f264aad0ca2679fe50c595615cb812a2adf35e0a6b425604a03c6eb5b018329b9958276a7bb0
6
+ metadata.gz: c9a92c82dd06087c092e8d5c02d752cad833d854be6268f0f5628ef70c98d837185e3ec9eec23e10ecd46073dd0dddc8e2275d9cfdcb6c44beb49107a2d003dd
7
+ data.tar.gz: a15adfd1f2cb6486a58c450a7e9bf7b104c74cbc597e968f064e1deaa8a39b7c14e451852ff463bf9487a88c59a0cdafebd1cedac9b95e657f14e87acb7dafbd
data/LICENSE.md CHANGED
@@ -1,9 +1,9 @@
1
- MIT License
2
-
3
- Copyright (c) 2013 Joel Pearson and Contributors.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
-
7
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2013 Joel Pearson and Contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,780 +1,783 @@
1
- RubyExcel
2
- =========
3
-
4
- Designed for Ruby on Windows with MS Excel
5
-
6
- Introduction
7
- ------------
8
-
9
- A Data-analysis tool for Ruby, with an Excel-style API.
10
-
11
- You can find the gem [here](https://rubygems.org/gems/rubyexcel "Rubygems").
12
-
13
- Main documentation is [here](http://rubydoc.info/gems/rubyexcel "Rubydoc")
14
-
15
- For any requests, comments, etc. I keep an eye on [this forum](http://www.ruby-forum.com/forum/ruby "Ruby Mailing List").
16
- If you put "RubyExcel" in the subject title I should see it.
17
-
18
- Please feel free to log any enhancement requests or bugs [here](https://github.com/VirtuosoJoel/RubyExcel/issues "Bug Tracker").
19
-
20
- Details
21
- -----
22
-
23
- This gem was made to simplify the steps between data extraction and the final output.
24
- You can drop the data into it, reorganise and edit it, and then output into Excel or your preferred file format.
25
- The methods provided for Excel interaction will return the relevant Excel object, allowing you to get as detailed as you like with the output.
26
-
27
- Key design features taken from Excel:
28
-
29
- * 1-based indexing.
30
- * Referencing objects like Excel's API ( Workbook, Sheet, Row, Column, Cell, Range ).
31
- * Useful data-handling functions ( e.g. Filter, Match, Sumif, Vlookup ).
32
-
33
- Typical usage:
34
-
35
- 1. Extract a HTML Table or CSV File into 2D Array ( normally with Nokogiri / Mechanize ).
36
- 2. Organise and interpret data with RubyExcel.
37
- 3. Output results into a file.
38
-
39
- About
40
- -----
41
-
42
- This gem is designed as a way to conveniently edit table data before outputting it to a variety of formats which Excel can interpret, including WIN32OLE Excel Workbooks.
43
- It attempts to take as much as possible from Excel's API while providing some of the best bits of Ruby ( e.g. Enumerators, Blocks, Regexp ).
44
- An important feature is allowing reference to Columns via their Headers for convenience and enhanced code readability.
45
- As this works directly on the data, processing is faster than using Excel itself.
46
-
47
- This was written out of the frustration of editing tabular data using Ruby's multidimensional arrays,
48
- without affecting headers and while maintaining code readability.
49
- Its API is designed to simplify moving code across from VBA into Ruby format when processing spreadsheet data.
50
- The combination of Ruby, WIN32OLE Excel, and analysing table data is probably quite rare; but I thought I'd share what I came up with.
51
-
52
- Examples
53
- ========
54
-
55
- Expected Data Layout (2D Array)
56
- --------
57
-
58
- ```ruby
59
- data = [
60
- [ 'Part', 'Ref1', 'Ref2', 'Qty', 'Cost' ],
61
- [ 'Type1', 'QT1', '231', 1, 35.15 ],
62
- [ 'Type2', 'QT3', '123', 1, 40 ],
63
- [ 'Type3', 'XT1', '321', 3, 0.1 ],
64
- [ 'Type1', 'XY2', '132', 1, 30.00 ],
65
- [ 'Type4', 'XT3', '312', 2, 3 ],
66
- [ 'Type2', 'QY2', '213', 1, 99.99 ],
67
- [ 'Type1', 'QT4', '123', 2, 104 ]
68
- ]
69
- ```
70
- The number of header rows defaults to 1
71
-
72
- Loading the data into a Sheet
73
- --------
74
-
75
- ```ruby
76
- require 'rubyexcel'
77
-
78
- wb = RubyExcel::Workbook.new
79
- s = wb.add( 'Sheet1' )
80
- s.load( data )
81
-
82
- Or:
83
-
84
- wb = RubyExcel::Workbook.new
85
- s = wb.add( 'Sheet1' )
86
- s.load( RubyExcel.sample_data )
87
-
88
- Or:
89
-
90
- wb = RubyExcel::Workbook.new
91
- s = wb.load( RubyExcel.sample_data )
92
-
93
- Or:
94
-
95
- s = RubyExcel.sample_sheet
96
- wb = s.parent
97
- ```
98
-
99
- Using the Mechanize gem to get data
100
- --------
101
-
102
- This example is for context, there are many potential data sources
103
-
104
- ```ruby
105
- s = RubyExcel::Workbook.new.load( CSV.parse( Mechanize.new.get('http://example.com/myfile.csv').content ) )
106
- ```
107
-
108
- Reference a cell's value
109
- --------
110
-
111
- ```ruby
112
- s['A7']
113
- s.A7
114
- s.cell(7,1).value
115
- s.range('A7').value
116
- s.row(7)['A']
117
- s.row(7)[1]
118
- s.column('A')[7]
119
- s.column('A')['7']
120
- ```
121
-
122
- Reference a group of cells
123
- --------
124
-
125
- ```ruby
126
- s['A1:B3'] #=> Array
127
- s.range( 'A1:B3' ) #=> Range
128
- s.range( 'A:A' ) #=> Range (Column)
129
- s.range( '1:2' ) #=> Range (Rows)
130
- s.range( 'A1', 'B3' ) #=> Range
131
- s.range( s.cell( 1, 1 ), s.cell( 3, 2 ) ) #=> Range
132
- s.row( 1 ) #=> Row
133
- s.row(1)[1] #=> Value
134
- s.row(1)[1, 2] #=> Array
135
- s.row(1)['A', 2] #=> Array
136
- s.row(1)[1..2] #=> Array
137
- s.row(1)['A'..'B'] #=> Array
138
- s.column( 'A' ) #=> Column
139
- s.column( 1 ) #=> Column
140
- s.column(1)[1] #=> Value
141
- s.column(1)[1, 2] #=> Array
142
- s.column(1)[1..2] #=> Array
143
- ```
144
-
145
- Using headers to reference the data
146
- --------
147
-
148
- Here we're looking for the "Part" in row 7
149
-
150
- ```ruby
151
- s.row(7).value_by_header( 'Part' ) #=> "Type2"
152
- s.row(7).val( 'Part' ) #=> "Type2"
153
-
154
- s.column_by_header( 'Part' )[7] #=> "Type2"
155
- s.ch( 'Part' )[7] #=> "Type2"
156
-
157
- s.row(7).cell_by_header( 'Part' ) #=> Cell A7
158
- s.row(7).cell_h( 'Part' ) #=> Cell A7
159
-
160
- s.row(7).getref('Part') #=> "A"
161
- ```
162
-
163
- Common Operations
164
- --------
165
-
166
- ```ruby
167
- #Some data to play with
168
- s = RubyExcel.sample_sheet
169
-
170
- #Have a look at the data
171
- puts s
172
-
173
- #Append a Column by adding a header
174
- s << 'Number'
175
-
176
- #Iterate through the rest of the rows while appending data
177
- x = 1
178
- s.rows(2) { |row| row << x; x+=1 }
179
-
180
- #Filter to specific part numbers
181
- s.filter!( 'Part', &/Type[1-3]/ )
182
-
183
- #Sort by Part Number
184
- s.sort_by!( 'Part' )
185
-
186
- #Add the Number to the Cost in each row.
187
- s.rows(2) { |row| row.cell_h('Cost').value += row.cell_h('Number').value }
188
-
189
- #Split the data into multiple sheets by part number
190
- wb = s.split( 'Part' )
191
-
192
- #Output a sheet as a TSV file
193
- File.write( 'Output.txt', wb.sheets(1).to_s )
194
-
195
- #Output a sheet as an HTML page
196
- File.write( 'Output.htm', wb.sheets(2).to_html )
197
-
198
- #Open a sheet in an Excel Workbook
199
- wb.sheets( 'Type3' ).to_excel
200
-
201
- #If you feel the need to delete everything between a set of rows:
202
- s.rows(2, 4).reverse_each &:delete
203
-
204
- #Gather text files containing string versions of RubyExcel::Sheet into a Workbook
205
- wb = RubyExcel::Workbook.new
206
- Dir.glob( '*.txt' ) do |f|
207
- data = File.read(f).split($/).map { |r| r.split("\t") }
208
- s = wb.add( f.chomp('.txt') )
209
- s.load data
210
- end
211
-
212
-
213
- ```
214
-
215
- Workbook
216
- --------
217
-
218
- ```ruby
219
- #Create a workbook
220
- wb = RubyExcel::Workbook.new
221
- wb = RubyExcel::Workbook.new( 'My Workbook' )
222
-
223
- #Get and set the name
224
- wb.name = 'My Workbook'
225
- wb.name #=> "My Workbook"
226
-
227
- #Add sheets to the workbook
228
- sheet1, sheet2 = wb.add('Sheet1'), wb.add
229
-
230
- #Delete all sheets from a workbook
231
- wb.clear_all
232
-
233
- #Delete a specific sheet
234
- wb.delete( 1 )
235
- wb.delete( 'Sheet1' )
236
- wb.delete( sheet1 )
237
- wb.delete( /sheet1/i )
238
- wb.delete { |sht| sht.name == 'Sheet1' }
239
-
240
- #Import a WIN32OLE Workbook or Sheet, either by passing the Object or a Filename
241
- #Parameters: WIN32OLE Object or Filename, SheetName or nil for all Sheets, true to keep Excel Formulas or omit to import Values.
242
- wb = RubyExcel.import( '/path/to/file.xlsx' )
243
- wb = RubyExcel.import( '/path/to/file.xlsx', 'Sheet2' )
244
- wb = RubyExcel.import( '/path/to/file.xlsx', 'Sheet2', true )
245
- #Or:
246
- require 'win32ole'
247
- excel = WIN32OLE.new( 'excel.application' )
248
- excel.visible = true #Optional
249
- my_workbook = excel.workbooks.open( '/path/to/file.xlsx' )
250
- wb.import( my_workbook )
251
- #Or:
252
- wb.import( my_workbook.sheets(1) )
253
-
254
- #Shortcut to create a sheet with a default name and fill it with data
255
- wb.load( data )
256
-
257
- #Select a sheet
258
- wb.sheets(1) #=> RubyExcel::Sheet
259
- wb.sheets('Sheet1') #=> RubyExcel::Sheet
260
-
261
- #Iterate through all sheets
262
- wb.sheets #=> Enumerator
263
- wb.each #=> Enumerator
264
-
265
- #Sort the sheets
266
- wb.sort! { |x,y| x.name <=> y.name }
267
- wb.sort_by! &:name
268
-
269
- #Output the workbook as a series of HTML tables
270
- wb.to_html
271
- ```
272
-
273
- Sheet
274
- --------
275
-
276
- ```ruby
277
- #Create a sheet
278
- s = wb.add #Name defaults to 'Sheet' + total number of sheets
279
- s = wb.add( 'Sheet1' )
280
-
281
- #Access the sheet name
282
- s.name #=> 'Sheet1'
283
- s.name = 'Sheet1'
284
-
285
- #Access the parent workbook
286
- s.workbook
287
- s.parent
288
-
289
- #Access the headers
290
- s.header_rows #=> 1
291
- s.headers #=> 1
292
- s.headers = 1
293
- s.header_rows = 1
294
-
295
- #Specify the number of header rows when loading data
296
- s.load( data, 1 )
297
-
298
- #Append data (at the bottom of the sheet)
299
- s << data
300
- s << s
301
- s += data
302
- s += s
303
-
304
- #Remove identical rows in another data set (skipping any headers)
305
- s -= data
306
- s -= s
307
-
308
- #Sheet#advanced_filter! is Deprecated. Sheet#filter! now accepts multiple arguments.
309
- #Filter on multiple criteria
310
- #You can add as many arguments as you like. The order is: Header, Method(Symbol), Argument
311
- #Note: Returns a copy of the sheet when used without "!".
312
- #Note: Sheet#filter is simpler to use, this is for the more in-depth stuff.
313
- _
314
- #Filter to Part 'Type1' and 'Type3' where Qty is greater than 1
315
- s.advanced_filter!( 'Part', :=~, /Type[13]/, 'Qty', :>, 1 )
316
- _
317
- #Filter to Part 'Type1' where Ref1 includes 'X'
318
- s.advanced_filter!( 'Part', :==, 'Type1', 'Ref1', :include?, 'X' )
319
-
320
- #Average all elements in a column by criteria in another column (selected by header)
321
- #Parameters: Header to pass to the block, Header to average, Block.
322
- #Note: Accepts Column objects in place of headers.
323
- s.averageif( 'Part', 'Cost' ) { |part| part == 'Type1' } #=> 56.38333333333333
324
- s.averageif( 'Part', 'Cost', &/Type1/ ) #=> 56.38333333333333
325
-
326
- #Select a column by its header
327
- s.column_by_header( 'Part' )
328
- s.ch( 'Part' )
329
- #=> Column
330
-
331
- #Iterate through rows or columns
332
- s.rows { |r| puts r } #All rows
333
- s.rows( 2 ) { |r| puts r } #From the 2nd to the last row
334
- s.rows( 1, 3 ) { |r| puts r } #Rows 1 to 3
335
- s.columns { |c| puts c } #All columns
336
- s.columns( 'B' ) { |c| puts c } #From the 2nd to the last column
337
- s.columns( 2 ) { |c| puts c } #From the 2nd to the last column
338
- s.columns( 'B', 'D' ) { |c| puts c } #Columns 2 to 4
339
- s.columns( 2, 4 ) { |c| puts c } #Columns 2 to 4
340
-
341
- #Count is included via Enumerable and accepts a block which will yield Row or Column.
342
- s.rows.count { |r| r.val('Part') == 'Type1' } #=> 3
343
- s.rows.count { |r| r.val('Part') == 'Type1' && r.val('Qty') < 2 } #=> 2
344
-
345
- #Remove all empty rows & columns
346
- s.compact!
347
-
348
- #Delete the current sheet from the workbook
349
- s.delete
350
-
351
- #Delete rows or columns "if( condition )" (iterates in reverse to preserve references during loop)
352
- s.delete_rows_if { |r| r.empty? }
353
- s.delete_columns_if { |c| c.empty? }
354
-
355
- #Filter the data given a column and a block to test values against.
356
- #Note: Returns a copy of the sheet when used without "!".
357
- #Note: This gem carries a Regexp to_proc method for Regex shorthand (shown below).
358
- s.filter!( 'Part' ) { |value| value =~ /Type[13]/ }
359
- s.filter!( 'Part', 'Cost' ) { |part, cost| part =~ /Type[13]/ && cost < 2 }
360
- s.filter!( 'Part', &/Type[13]/ )
361
-
362
- #Filter the data to a specific set of columns by their headers.
363
- #Note: Returns a copy of the sheet when used without "!".
364
- s.get_columns!( 'Cost', 'Part', 'Qty' )
365
- s.gc!( 'Cost', 'Part', 'Qty' )
366
-
367
- #Insert blank rows or columns ( before, number to insert )
368
- s.insert_rows( 2, 2 ) #Inserts 2 empty rows before row 2
369
- s.insert_columns( 'B', 1 ) #Inserts 2 empty columns before column 2
370
- s.insert_columns( 2, 1 ) #Inserts 2 empty columns before column 2
371
-
372
- #Find the first row which matches a value within a column (selected by header)
373
- #Note: Can now accept a Column object in place of a header.
374
- s.match( 'Qty' ) { |value| value == 1 } #=> 2
375
- s.match( 'Part', &/Type2/ ) #=> 3
376
-
377
- #Find the current end of the data range
378
- s.maxrow #=> 8
379
- s.rows.count #=> 8
380
- s.maxcol #=> 5
381
- s.columns.count #=> 5
382
-
383
- #Partition the sheet into two, given a header and a block (like Filter)
384
- #Note: this keeps the headers intact in both output sheets
385
- type_1_and_3, other = s.partition( 'Part' ) { |value| value =~ /Type[13]/ }
386
- type_1_and_3, other = s.partition( 'Part', &/Type[13]/ )
387
-
388
- #Reverse the data by rows or columns (ignores headers)
389
- s.reverse_rows!
390
- s.reverse_columns!
391
-
392
- #Sort the rows by header(s) (ignores header rows)
393
- s.sort_by!( 'Part' )
394
- s.sort_by!( 'Qty', 'Part' )
395
-
396
- #Split a Sheet into a Workbook of Sheets by a column (selected by header)
397
- wb = s.split( 'Part' )
398
- #=> <Workbook: [Sheet:Type1, Sheet:Type2, Sheet:Type3, Sheet:Type4]>
399
-
400
- #Sum all elements in a column by criteria in another column (selected by header)
401
- #Parameters: Header to pass to the block, Header to sum, Block.
402
- #Note: Now also accepts Column objects in place of headers.
403
- s.sumif( 'Part', 'Cost' ) { |part| part == 'Type1' } #=> 169.15
404
- s.sumif( 'Part', 'Cost', &/Type1/ ) #=> 169.15
405
-
406
- #Summarise a column by header into a Hash.
407
- s.summarise( 'Part' )
408
- #=> {"Type1"=>3, "Type2"=>2, "Type3"=>1, "Type4"=>1}
409
-
410
- #Convert the data into various formats:
411
- s.to_a #=> 2D Array
412
- s.to_excel #=> WIN32OLE Excel Workbook (Contains only the current sheet)
413
- s.to_html #=> String (HTML table)
414
- s.to_s #=> String (TSV)
415
-
416
- #Remove all rows with duplicate values in the given column (selected by header or Column object)
417
- s.uniq! 'Part'
418
-
419
- #Find a value in one column by searching another one (selected by headers or Column objects)
420
- s.vlookup( 'Part', 'Ref1', &/Type4/ ) #=> "XT3"
421
- ```
422
-
423
- Row / Column (Section)
424
- --------
425
-
426
- ```ruby
427
- #Reference a Row or Column
428
- row = s.row(2)
429
- col = s.column('B')
430
-
431
- #Read a value
432
- row[1] #=> "Type1"
433
- row['A'] #=> "Type1"
434
- col[1] #=> "Ref1"
435
-
436
- #Read multiple values
437
- row[1..2] #=> ["Type1", "QT1"]
438
- row[1, 2] #=> ["Type1", "QT1"]
439
- row['A', 2] #=> ["Type1", "QT1"]
440
- col[1..2] #=> ["Ref1", "QT1"]
441
- col[1, 2] #=> ["Ref1", "QT1"]
442
-
443
- #Write a value
444
- row[1] = "Type1"
445
- row['A'] = "Type1"
446
- col[1] = "Ref1"
447
-
448
- #Write multiple values
449
- row[1..2] = "Type1", "QT1"
450
- row[1, 2] = "Type1", "QT1"
451
- row['A', 2] = "Type1", "QT1"
452
- col[1..2] = "Ref1", "QT1"
453
- col[1, 2] = "Ref1", "QT1"
454
-
455
- =begin
456
- Append a value
457
- Note: Only extends the data boundaries when at the first row or column.
458
- This allows looping through an entire row or column to append single values,
459
- without worrying about using the correct index.
460
- =end
461
- s.row(1) << 'New'
462
- s.rows(2) { |r| r << 'Column' }
463
- s.column(1) << 'New'
464
- s.columns(2) { |c| c << 'Row' }
465
-
466
- #Access a cell by column header (Row only)
467
- s.row(2).cell_by_header( 'Part' ) #=> Cell A2
468
- s.row(2).cell_h( 'Cost' ) #=> Cell E2
469
-
470
- #Delete the data referenced by self.
471
- row.delete
472
- col.delete
473
-
474
- #Find the address of a cell matching a block
475
- row.find { |value| value == 'QT1' }
476
- row.find &/QT1/
477
- col.find { |value| value == 'QT1' }
478
- col.find &/QT1/
479
-
480
- #Summarise the current row or column into a Hash.
481
- s.column(1).summarise
482
- #=> {"Type1"=>3, "Type2"=>2, "Type3"=>1, "Type4"=>1}
483
-
484
- #Loop through all values
485
- row.each { |val| puts val }
486
- col.each { |val| puts val }
487
-
488
- #Loop through all values without including headers
489
- col.each_without_headers { |val| puts val }
490
- col.each_wh { |val| puts val }
491
-
492
- #Loop through each cell
493
- row.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
494
- col.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
495
-
496
- #Loop through each cell without including headers
497
- col.each_cell_without_headers { |ce| puts "#{ ce.address }: #{ ce.value }" }
498
- col.each_cell_wh { |ce| puts "#{ ce.address }: #{ ce.value }" }
499
-
500
- #Get the letter of a column by its header ( helps build an address when combined with row.idx )
501
- row.getref( 'Part' ) #=> "A"
502
-
503
- #Overwrite each value based on its current value
504
- row.map! { |val| val.to_s + 'a' }
505
- col.map! { |val| val.to_s + 'a' }
506
-
507
- #Get the value of a cell in the current row by its header
508
- row.value_by_header( 'Part' ) #=> 'Type1'
509
- row.val( 'Part' ) #=> 'Type1'
510
-
511
- #Set the value of a cell in the current row by its header
512
- row.set_value_by_header( 'Part', 'Type5' )
513
- row.set_val( 'Part', 'Type5' )
514
- ```
515
-
516
- Cell / Range (Element)
517
- --------
518
-
519
- ```ruby
520
- #Reference a Cell or Range
521
- #If you select a single-Cell Range you get a Cell
522
- cell = s.cell( 2, 2 ) #=> Cell
523
- s.range('B2') #=> Cell
524
- range = s.range('B2:C3') #=> Range
525
-
526
- #Get the address and indices of the Element (Indices return that of the first Cell in a Range)
527
- cell.address
528
- cell.row
529
- cell.column
530
- range.address
531
- range.row
532
- range.column
533
-
534
- #Get and set the value(s)
535
- cell.value #=> "QT1"
536
- cell.value = 'QT1'
537
- range.value #=> [["QT1", "231"], ["QT3", "123"]]
538
- range.value = "a"
539
- range.value #=> [["a", "a"], ["a", "a"]]
540
- range.value = [["QT1", "231"], ["QT3", "123"]]
541
- range.value #=> [["QT1", "231"], ["QT3", "123"]]
542
-
543
- #Loop through a range
544
- range.each { |val| puts val }
545
-
546
- #Loop through each cell within a range
547
- range.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
548
-
549
- ```
550
-
551
- Address Tools (Included in Sheet, Section, and Element)
552
- --------
553
-
554
- ```ruby
555
- #Get the column index from an address string
556
- s.address_to_col_index( 'A2' ) #=> 1
557
-
558
- #Translate an address to indices
559
- s.address_to_indices( 'A2' ) #=> [ 2, 1 ]
560
-
561
- #Translate letter(s) to a column index
562
- s.col_index( 'A' ) #=> 1
563
-
564
- #Translate a number to column letter(s)
565
- s.col_letter( 1 ) #=> "A"
566
-
567
- #Extract the column letter(s) or row number from an address
568
- s.column_id( 'A2' ) #=> "A"
569
- s.row_id( 'A2' ) #=> 2
570
-
571
- #Expand a Range address
572
- s.expand( 'A1:B2' ) #=> [["A1", "B1"], ["A2","B2"]]
573
- s.expand( 'A1' ) #=> [["A1"]]
574
-
575
- #Translate indices to an address
576
- s.indices_to_address( 2, 1 ) #=> "A2"
577
-
578
- #Offset an address by rows and columns
579
- s.offset( 'A2', 1, 2 ) #=> "C3"
580
- s.offset( 'A2', 2, 0 ) #=> "A4"
581
- s.offset( 'A2', -1, 0 ) #=> "A1"
582
-
583
- #Here's a trick you can try if you're going to use the Address tools a lot:
584
- include RubyExcel::Address
585
- #Now you can use all the address methods in the current scope:
586
- offset( 'C3', 1, -2 ) #=> A4
587
-
588
- ```
589
-
590
- Importing a Hash
591
- --------
592
-
593
- ```ruby
594
- #Import a nested Hash (useful if you're summarising data before handing it to RubyExcel)
595
-
596
- #Here's an example Hash (built into the gem as RubyExcel.sample_hash)
597
- h = {
598
- Part1: {
599
- Type1: {
600
- SubType1: 1, SubType2: 2, SubType3: 3
601
- },
602
- Type2: {
603
- SubType1: 4, SubType2: 5, SubType3: 6
604
- }
605
- },
606
- Part2: {
607
- Type1: {
608
- SubType1: 1, SubType2: 2, SubType3: 3
609
- },
610
- Type2: {
611
- SubType1: 4, SubType2: 5, SubType3: 6
612
- }
613
- }
614
- }
615
-
616
- #Import the Hash to a Sheet
617
- s.load( h )
618
- #Or append the Hash to a Sheet
619
- s << h
620
-
621
- #Convert the symbols to strings (Not essential, but Excel can't handle Symbols in output)
622
- s.rows { |r| r.map! { |v| v.is_a?(Symbol) ? v.to_s : v } }
623
-
624
- #Have a look at the results
625
- require 'pp'
626
- pp s.to_a
627
- [["Part1", "Type1", "SubType1", 1],
628
- ["Part1", "Type1", "SubType2", 2],
629
- ["Part1", "Type1", "SubType3", 3],
630
- ["Part1", "Type2", "SubType1", 4],
631
- ["Part1", "Type2", "SubType2", 5],
632
- ["Part1", "Type2", "SubType3", 6],
633
- ["Part2", "Type1", "SubType1", 1],
634
- ["Part2", "Type1", "SubType2", 2],
635
- ["Part2", "Type1", "SubType3", 3],
636
- ["Part2", "Type2", "SubType1", 4],
637
- ["Part2", "Type2", "SubType2", 5],
638
- ["Part2", "Type2", "SubType3", 6]]
639
-
640
- ```
641
-
642
- Excel Tools ( requires win32ole and Excel )
643
- --------
644
-
645
- Make sure all your data types are compatible with Excel first (see Workbook#to_safe_format!)
646
-
647
- RubyExcel should only attempt to require win32ole if one of these methods is called.
648
- This should make it compatible as a data structure on non-windows systems.
649
-
650
- ```ruby
651
-
652
- #Sample RubyExcel::Workbook to work with
653
- rubywb = RubyExcel.sample_sheet.parent
654
-
655
- #Get a new Excel instance
656
- excel = rubywb.get_excel
657
-
658
- #Get a new Excel Workbook
659
- excelwb = rubywb.get_workbook( excel )
660
- excelwb = rubywb.get_workbook
661
-
662
- #Adds a leading single quote to any Strings with a leading Equals sign.
663
- #This should prevent Excel from throwing Exceptions about invalid data such as "==".
664
- rubywb.disable_formulas!
665
-
666
- #Drop data into an Excel Sheet
667
- #Note: The optional 2nd argument lets you give it an Excel sheet to use
668
- rubywb.dump_to_sheet( rubywb.sheets(1).to_a )
669
- rubywb.dump_to_sheet( rubywb.sheets(1).to_a, excelwb.sheets(1) )
670
-
671
- #Autofit, left-align, and border a WIN32OLE Excel Sheet
672
- rubywb.make_sheet_pretty( excelwb.sheets(1) )
673
-
674
- #Output the RubyExcel::Workbook into a new Excel Workbook
675
- rubywb.to_excel
676
-
677
- #Output the RubyExcel::Sheet into a new Excel Workbook
678
- rubywb.sheets(1).to_excel
679
-
680
- #Output the RubyExcel::Workbook into an Excel Workbook and save the file
681
- #Note: The default directory is "Documents" or "My Documents" to support Ocra + InnoSetup installs.
682
- #The Workbook name is used as the filename unless an argument is passed.
683
- #Note: There is an optional second argument which if set to true doesn't make Excel visible.
684
- #This is a useful accelerator when running as an automated process.
685
- #If you set the process to be invisible, don't forget to close Excel after you're finished with it!
686
- rubywb.save_excel
687
- rubywb.save_excel( 'Output' )
688
- rubywb.save_excel( 'c:/example/Output.xlsx' )
689
- rubywb.save_excel( 'Output', true ) # ( Keeps Excel invisible if creating a new instance )
690
-
691
- #Convert all internal data to Strings and disable leading equals signs
692
- #This should ensure compatibility with Excel for most types of data.
693
- #Note: This will disable any formulas currently in the Workbook.
694
- #Note: This modifies the data inside the Workbook. For a non-destructive version, leave off the exclamation mark.
695
- rubywb.to_safe_format!
696
-
697
- #Add borders to a given Excel Range
698
- #1st Argument: WIN32OLE Range
699
- #2nd Argument (default 1), weight of borders (0 to 4)
700
- #3rd Argument (default false), include inner borders
701
- RubyExcel.borders( excelwb.sheets(1).usedrange ) #Give used range outer borders
702
- RubyExcel.borders( excelwb.sheets(1).usedrange, 2, true ) #Give used range inner and outer borders, medium weight
703
- RubyExcel.borders( excelwb.sheets(1).usedrange, 0, false ) #Clear outer borders from used range
704
-
705
- #You can even enter formula strings and Excel will evaluate them in the output.
706
- s = rubywb.sheets(1)
707
- s.row(1) << 'Formula'
708
- s.rows(2) { |row| row << "=SUM(D#{ row.idx }:E#{ row.idx })" }
709
- s.to_excel
710
-
711
- ```
712
-
713
- Comparison of operations with and without RubyExcel gem
714
- --------
715
-
716
- Without RubyExcel (one way to to it):
717
-
718
- ```ruby
719
- #Filter to only 'Part' of 'Type1' and 'Type3' while keeping the header row
720
- idx = data[0].index( 'Part' )
721
- data = [ data[0] ] + data[1..-1].select { |row| row[ idx ] =~ /Type[13]/ }
722
-
723
- #Keep only the columns 'Cost' and 'Ref2' in that order
724
- max_size = data.max_by(&:length).length #Standardise the row size to transpose into columns
725
- data.map! { |row| row.length == max_size ? row : row + Array.new( max_size - row.length, nil) }
726
- headers = [ 'Cost', 'Ref2' ]
727
- data = data.transpose.select { |header,_| headers.index(header) }.sort_by { |header,_| headers.index(header) }.transpose
728
-
729
- #Get the combined 'Cost' of every 'Part' of 'Type1' and 'Type3'
730
- find_idx, sum_idx = data[0].index('Part'), data[0].index('Cost')
731
- data[1..-1].inject(0) { |sum, row| row[find_idx] =~ /Type[13]/ ? sum + row[sum_idx] : sum }
732
-
733
- #Write the data to a TSV file
734
- output = data.map { |row| row.map { |el| "#{el}".strip.gsub( /\s/, ' ' ) }.join "\t" }.join $/
735
- File.write( 'output.txt', output )
736
-
737
- #Drop the data into an Excel sheet ( using Excel and win32ole )
738
- excel = WIN32OLE::new( 'excel.application' )
739
- excel.visible = true
740
- wb = excel.workbooks.add
741
- sheet = wb.sheets(1)
742
- sheet.range( sheet.cells( 1, 1 ), sheet.cells( data.length, data[0].length ) ).value = data
743
- #Excel should automatically target the "Documents" / "My Documents" folder when using a new instance.
744
- #Excel should use the default file extension for the version it's using.
745
- wb.saveas( 'Output' )
746
- ```
747
-
748
- With RubyExcel:
749
-
750
- ```ruby
751
- #Filter to only 'Part' of 'Type1' and 'Type3' while keeping the header row
752
- s.filter!( 'Part', &/Type[13]/ )
753
-
754
- #Keep only the columns 'Cost' and 'Ref2' in that order
755
- s.get_columns!( 'Cost', 'Ref2' )
756
-
757
- #Get the combined 'Cost' of every 'Part' of 'Type1' and 'Type3'
758
- s.sumif( 'Part', 'Cost', &/Type[13]/ )
759
-
760
- #Write the data to a TSV file
761
- File.write( 'output.txt', s.to_s )
762
-
763
- #Write the data to an Excel file ( requires Excel and win32ole )
764
- #This aims at "Documents" or "My Documents" by default and uses the Workbook's "name" attribute.
765
- s.parent.save_excel
766
- ```
767
-
768
- Todo List
769
- =========
770
-
771
- - Find bugs and extirpate them.
772
-
773
- - Optimise slow operations
774
-
775
-
776
- -----------
777
-
778
- Copyright (c) 2013, Joel Pearson and Contributors. All Rights Reserved.
779
-
1
+ RubyExcel
2
+ =========
3
+
4
+ Designed for Ruby on Windows with MS Excel
5
+
6
+ Note: This project is on indefinite hold as of 05/02/2014, as Joel Pearson is no longer actively involved.
7
+ If anyone wishes to continue development, please feel free to fork the project.
8
+
9
+ Introduction
10
+ ------------
11
+
12
+ A Data-analysis tool for Ruby, with an Excel-style API.
13
+
14
+ You can find the gem [here](https://rubygems.org/gems/rubyexcel "Rubygems").
15
+
16
+ Main documentation is [here](http://rubydoc.info/gems/rubyexcel "Rubydoc")
17
+
18
+ For any requests, comments, etc. I keep an eye on [this forum](http://www.ruby-forum.com/forum/ruby "Ruby Mailing List").
19
+ If you put "RubyExcel" in the subject title I should see it.
20
+
21
+ Please feel free to log any enhancement requests or bugs [here](https://github.com/VirtuosoJoel/RubyExcel/issues "Bug Tracker").
22
+
23
+ Details
24
+ -----
25
+
26
+ This gem was made to simplify the steps between data extraction and the final output.
27
+ You can drop the data into it, reorganise and edit it, and then output into Excel or your preferred file format.
28
+ The methods provided for Excel interaction will return the relevant Excel object, allowing you to get as detailed as you like with the output.
29
+
30
+ Key design features taken from Excel:
31
+
32
+ * 1-based indexing.
33
+ * Referencing objects like Excel's API ( Workbook, Sheet, Row, Column, Cell, Range ).
34
+ * Useful data-handling functions ( e.g. Filter, Match, Sumif, Vlookup ).
35
+
36
+ Typical usage:
37
+
38
+ 1. Extract a HTML Table or CSV File into 2D Array ( normally with Nokogiri / Mechanize ).
39
+ 2. Organise and interpret data with RubyExcel.
40
+ 3. Output results into a file.
41
+
42
+ About
43
+ -----
44
+
45
+ This gem is designed as a way to conveniently edit table data before outputting it to a variety of formats which Excel can interpret, including WIN32OLE Excel Workbooks.
46
+ It attempts to take as much as possible from Excel's API while providing some of the best bits of Ruby ( e.g. Enumerators, Blocks, Regexp ).
47
+ An important feature is allowing reference to Columns via their Headers for convenience and enhanced code readability.
48
+ As this works directly on the data, processing is faster than using Excel itself.
49
+
50
+ This was written out of the frustration of editing tabular data using Ruby's multidimensional arrays,
51
+ without affecting headers and while maintaining code readability.
52
+ Its API is designed to simplify moving code across from VBA into Ruby format when processing spreadsheet data.
53
+ The combination of Ruby, WIN32OLE Excel, and analysing table data is probably quite rare; but I thought I'd share what I came up with.
54
+
55
+ Examples
56
+ ========
57
+
58
+ Expected Data Layout (2D Array)
59
+ --------
60
+
61
+ ```ruby
62
+ data = [
63
+ [ 'Part', 'Ref1', 'Ref2', 'Qty', 'Cost' ],
64
+ [ 'Type1', 'QT1', '231', 1, 35.15 ],
65
+ [ 'Type2', 'QT3', '123', 1, 40 ],
66
+ [ 'Type3', 'XT1', '321', 3, 0.1 ],
67
+ [ 'Type1', 'XY2', '132', 1, 30.00 ],
68
+ [ 'Type4', 'XT3', '312', 2, 3 ],
69
+ [ 'Type2', 'QY2', '213', 1, 99.99 ],
70
+ [ 'Type1', 'QT4', '123', 2, 104 ]
71
+ ]
72
+ ```
73
+ The number of header rows defaults to 1
74
+
75
+ Loading the data into a Sheet
76
+ --------
77
+
78
+ ```ruby
79
+ require 'rubyexcel'
80
+
81
+ wb = RubyExcel::Workbook.new
82
+ s = wb.add( 'Sheet1' )
83
+ s.load( data )
84
+
85
+ Or:
86
+
87
+ wb = RubyExcel::Workbook.new
88
+ s = wb.add( 'Sheet1' )
89
+ s.load( RubyExcel.sample_data )
90
+
91
+ Or:
92
+
93
+ wb = RubyExcel::Workbook.new
94
+ s = wb.load( RubyExcel.sample_data )
95
+
96
+ Or:
97
+
98
+ s = RubyExcel.sample_sheet
99
+ wb = s.parent
100
+ ```
101
+
102
+ Using the Mechanize gem to get data
103
+ --------
104
+
105
+ This example is for context, there are many potential data sources
106
+
107
+ ```ruby
108
+ s = RubyExcel::Workbook.new.load( CSV.parse( Mechanize.new.get('http://example.com/myfile.csv').content ) )
109
+ ```
110
+
111
+ Reference a cell's value
112
+ --------
113
+
114
+ ```ruby
115
+ s['A7']
116
+ s.A7
117
+ s.cell(7,1).value
118
+ s.range('A7').value
119
+ s.row(7)['A']
120
+ s.row(7)[1]
121
+ s.column('A')[7]
122
+ s.column('A')['7']
123
+ ```
124
+
125
+ Reference a group of cells
126
+ --------
127
+
128
+ ```ruby
129
+ s['A1:B3'] #=> Array
130
+ s.range( 'A1:B3' ) #=> Range
131
+ s.range( 'A:A' ) #=> Range (Column)
132
+ s.range( '1:2' ) #=> Range (Rows)
133
+ s.range( 'A1', 'B3' ) #=> Range
134
+ s.range( s.cell( 1, 1 ), s.cell( 3, 2 ) ) #=> Range
135
+ s.row( 1 ) #=> Row
136
+ s.row(1)[1] #=> Value
137
+ s.row(1)[1, 2] #=> Array
138
+ s.row(1)['A', 2] #=> Array
139
+ s.row(1)[1..2] #=> Array
140
+ s.row(1)['A'..'B'] #=> Array
141
+ s.column( 'A' ) #=> Column
142
+ s.column( 1 ) #=> Column
143
+ s.column(1)[1] #=> Value
144
+ s.column(1)[1, 2] #=> Array
145
+ s.column(1)[1..2] #=> Array
146
+ ```
147
+
148
+ Using headers to reference the data
149
+ --------
150
+
151
+ Here we're looking for the "Part" in row 7
152
+
153
+ ```ruby
154
+ s.row(7).value_by_header( 'Part' ) #=> "Type2"
155
+ s.row(7).val( 'Part' ) #=> "Type2"
156
+
157
+ s.column_by_header( 'Part' )[7] #=> "Type2"
158
+ s.ch( 'Part' )[7] #=> "Type2"
159
+
160
+ s.row(7).cell_by_header( 'Part' ) #=> Cell A7
161
+ s.row(7).cell_h( 'Part' ) #=> Cell A7
162
+
163
+ s.row(7).getref('Part') #=> "A"
164
+ ```
165
+
166
+ Common Operations
167
+ --------
168
+
169
+ ```ruby
170
+ #Some data to play with
171
+ s = RubyExcel.sample_sheet
172
+
173
+ #Have a look at the data
174
+ puts s
175
+
176
+ #Append a Column by adding a header
177
+ s << 'Number'
178
+
179
+ #Iterate through the rest of the rows while appending data
180
+ x = 1
181
+ s.rows(2) { |row| row << x; x+=1 }
182
+
183
+ #Filter to specific part numbers
184
+ s.filter!( 'Part', &/Type[1-3]/ )
185
+
186
+ #Sort by Part Number
187
+ s.sort_by!( 'Part' )
188
+
189
+ #Add the Number to the Cost in each row.
190
+ s.rows(2) { |row| row.cell_h('Cost').value += row.cell_h('Number').value }
191
+
192
+ #Split the data into multiple sheets by part number
193
+ wb = s.split( 'Part' )
194
+
195
+ #Output a sheet as a TSV file
196
+ File.write( 'Output.txt', wb.sheets(1).to_s )
197
+
198
+ #Output a sheet as an HTML page
199
+ File.write( 'Output.htm', wb.sheets(2).to_html )
200
+
201
+ #Open a sheet in an Excel Workbook
202
+ wb.sheets( 'Type3' ).to_excel
203
+
204
+ #If you feel the need to delete everything between a set of rows:
205
+ s.rows(2, 4).reverse_each &:delete
206
+
207
+ #Gather text files containing string versions of RubyExcel::Sheet into a Workbook
208
+ wb = RubyExcel::Workbook.new
209
+ Dir.glob( '*.txt' ) do |f|
210
+ data = File.read(f).split($/).map { |r| r.split("\t") }
211
+ s = wb.add( f.chomp('.txt') )
212
+ s.load data
213
+ end
214
+
215
+
216
+ ```
217
+
218
+ Workbook
219
+ --------
220
+
221
+ ```ruby
222
+ #Create a workbook
223
+ wb = RubyExcel::Workbook.new
224
+ wb = RubyExcel::Workbook.new( 'My Workbook' )
225
+
226
+ #Get and set the name
227
+ wb.name = 'My Workbook'
228
+ wb.name #=> "My Workbook"
229
+
230
+ #Add sheets to the workbook
231
+ sheet1, sheet2 = wb.add('Sheet1'), wb.add
232
+
233
+ #Delete all sheets from a workbook
234
+ wb.clear_all
235
+
236
+ #Delete a specific sheet
237
+ wb.delete( 1 )
238
+ wb.delete( 'Sheet1' )
239
+ wb.delete( sheet1 )
240
+ wb.delete( /sheet1/i )
241
+ wb.delete { |sht| sht.name == 'Sheet1' }
242
+
243
+ #Import a WIN32OLE Workbook or Sheet, either by passing the Object or a Filename
244
+ #Parameters: WIN32OLE Object or Filename, SheetName or nil for all Sheets, true to keep Excel Formulas or omit to import Values.
245
+ wb = RubyExcel.import( '/path/to/file.xlsx' )
246
+ wb = RubyExcel.import( '/path/to/file.xlsx', 'Sheet2' )
247
+ wb = RubyExcel.import( '/path/to/file.xlsx', 'Sheet2', true )
248
+ #Or:
249
+ require 'win32ole'
250
+ excel = WIN32OLE.new( 'excel.application' )
251
+ excel.visible = true #Optional
252
+ my_workbook = excel.workbooks.open( '/path/to/file.xlsx' )
253
+ wb.import( my_workbook )
254
+ #Or:
255
+ wb.import( my_workbook.sheets(1) )
256
+
257
+ #Shortcut to create a sheet with a default name and fill it with data
258
+ wb.load( data )
259
+
260
+ #Select a sheet
261
+ wb.sheets(1) #=> RubyExcel::Sheet
262
+ wb.sheets('Sheet1') #=> RubyExcel::Sheet
263
+
264
+ #Iterate through all sheets
265
+ wb.sheets #=> Enumerator
266
+ wb.each #=> Enumerator
267
+
268
+ #Sort the sheets
269
+ wb.sort! { |x,y| x.name <=> y.name }
270
+ wb.sort_by! &:name
271
+
272
+ #Output the workbook as a series of HTML tables
273
+ wb.to_html
274
+ ```
275
+
276
+ Sheet
277
+ --------
278
+
279
+ ```ruby
280
+ #Create a sheet
281
+ s = wb.add #Name defaults to 'Sheet' + total number of sheets
282
+ s = wb.add( 'Sheet1' )
283
+
284
+ #Access the sheet name
285
+ s.name #=> 'Sheet1'
286
+ s.name = 'Sheet1'
287
+
288
+ #Access the parent workbook
289
+ s.workbook
290
+ s.parent
291
+
292
+ #Access the headers
293
+ s.header_rows #=> 1
294
+ s.headers #=> 1
295
+ s.headers = 1
296
+ s.header_rows = 1
297
+
298
+ #Specify the number of header rows when loading data
299
+ s.load( data, 1 )
300
+
301
+ #Append data (at the bottom of the sheet)
302
+ s << data
303
+ s << s
304
+ s += data
305
+ s += s
306
+
307
+ #Remove identical rows in another data set (skipping any headers)
308
+ s -= data
309
+ s -= s
310
+
311
+ #Sheet#advanced_filter! is Deprecated. Sheet#filter! now accepts multiple arguments.
312
+ #Filter on multiple criteria
313
+ #You can add as many arguments as you like. The order is: Header, Method(Symbol), Argument
314
+ #Note: Returns a copy of the sheet when used without "!".
315
+ #Note: Sheet#filter is simpler to use, this is for the more in-depth stuff.
316
+ _
317
+ #Filter to Part 'Type1' and 'Type3' where Qty is greater than 1
318
+ s.advanced_filter!( 'Part', :=~, /Type[13]/, 'Qty', :>, 1 )
319
+ _
320
+ #Filter to Part 'Type1' where Ref1 includes 'X'
321
+ s.advanced_filter!( 'Part', :==, 'Type1', 'Ref1', :include?, 'X' )
322
+
323
+ #Average all elements in a column by criteria in another column (selected by header)
324
+ #Parameters: Header to pass to the block, Header to average, Block.
325
+ #Note: Accepts Column objects in place of headers.
326
+ s.averageif( 'Part', 'Cost' ) { |part| part == 'Type1' } #=> 56.38333333333333
327
+ s.averageif( 'Part', 'Cost', &/Type1/ ) #=> 56.38333333333333
328
+
329
+ #Select a column by its header
330
+ s.column_by_header( 'Part' )
331
+ s.ch( 'Part' )
332
+ #=> Column
333
+
334
+ #Iterate through rows or columns
335
+ s.rows { |r| puts r } #All rows
336
+ s.rows( 2 ) { |r| puts r } #From the 2nd to the last row
337
+ s.rows( 1, 3 ) { |r| puts r } #Rows 1 to 3
338
+ s.columns { |c| puts c } #All columns
339
+ s.columns( 'B' ) { |c| puts c } #From the 2nd to the last column
340
+ s.columns( 2 ) { |c| puts c } #From the 2nd to the last column
341
+ s.columns( 'B', 'D' ) { |c| puts c } #Columns 2 to 4
342
+ s.columns( 2, 4 ) { |c| puts c } #Columns 2 to 4
343
+
344
+ #Count is included via Enumerable and accepts a block which will yield Row or Column.
345
+ s.rows.count { |r| r.val('Part') == 'Type1' } #=> 3
346
+ s.rows.count { |r| r.val('Part') == 'Type1' && r.val('Qty') < 2 } #=> 2
347
+
348
+ #Remove all empty rows & columns
349
+ s.compact!
350
+
351
+ #Delete the current sheet from the workbook
352
+ s.delete
353
+
354
+ #Delete rows or columns "if( condition )" (iterates in reverse to preserve references during loop)
355
+ s.delete_rows_if { |r| r.empty? }
356
+ s.delete_columns_if { |c| c.empty? }
357
+
358
+ #Filter the data given a column and a block to test values against.
359
+ #Note: Returns a copy of the sheet when used without "!".
360
+ #Note: This gem carries a Regexp to_proc method for Regex shorthand (shown below).
361
+ s.filter!( 'Part' ) { |value| value =~ /Type[13]/ }
362
+ s.filter!( 'Part', 'Cost' ) { |part, cost| part =~ /Type[13]/ && cost < 2 }
363
+ s.filter!( 'Part', &/Type[13]/ )
364
+
365
+ #Filter the data to a specific set of columns by their headers.
366
+ #Note: Returns a copy of the sheet when used without "!".
367
+ s.get_columns!( 'Cost', 'Part', 'Qty' )
368
+ s.gc!( 'Cost', 'Part', 'Qty' )
369
+
370
+ #Insert blank rows or columns ( before, number to insert )
371
+ s.insert_rows( 2, 2 ) #Inserts 2 empty rows before row 2
372
+ s.insert_columns( 'B', 1 ) #Inserts 2 empty columns before column 2
373
+ s.insert_columns( 2, 1 ) #Inserts 2 empty columns before column 2
374
+
375
+ #Find the first row which matches a value within a column (selected by header)
376
+ #Note: Can now accept a Column object in place of a header.
377
+ s.match( 'Qty' ) { |value| value == 1 } #=> 2
378
+ s.match( 'Part', &/Type2/ ) #=> 3
379
+
380
+ #Find the current end of the data range
381
+ s.maxrow #=> 8
382
+ s.rows.count #=> 8
383
+ s.maxcol #=> 5
384
+ s.columns.count #=> 5
385
+
386
+ #Partition the sheet into two, given a header and a block (like Filter)
387
+ #Note: this keeps the headers intact in both output sheets
388
+ type_1_and_3, other = s.partition( 'Part' ) { |value| value =~ /Type[13]/ }
389
+ type_1_and_3, other = s.partition( 'Part', &/Type[13]/ )
390
+
391
+ #Reverse the data by rows or columns (ignores headers)
392
+ s.reverse_rows!
393
+ s.reverse_columns!
394
+
395
+ #Sort the rows by header(s) (ignores header rows)
396
+ s.sort_by!( 'Part' )
397
+ s.sort_by!( 'Qty', 'Part' )
398
+
399
+ #Split a Sheet into a Workbook of Sheets by a column (selected by header)
400
+ wb = s.split( 'Part' )
401
+ #=> <Workbook: [Sheet:Type1, Sheet:Type2, Sheet:Type3, Sheet:Type4]>
402
+
403
+ #Sum all elements in a column by criteria in another column (selected by header)
404
+ #Parameters: Header to pass to the block, Header to sum, Block.
405
+ #Note: Now also accepts Column objects in place of headers.
406
+ s.sumif( 'Part', 'Cost' ) { |part| part == 'Type1' } #=> 169.15
407
+ s.sumif( 'Part', 'Cost', &/Type1/ ) #=> 169.15
408
+
409
+ #Summarise a column by header into a Hash.
410
+ s.summarise( 'Part' )
411
+ #=> {"Type1"=>3, "Type2"=>2, "Type3"=>1, "Type4"=>1}
412
+
413
+ #Convert the data into various formats:
414
+ s.to_a #=> 2D Array
415
+ s.to_excel #=> WIN32OLE Excel Workbook (Contains only the current sheet)
416
+ s.to_html #=> String (HTML table)
417
+ s.to_s #=> String (TSV)
418
+
419
+ #Remove all rows with duplicate values in the given column (selected by header or Column object)
420
+ s.uniq! 'Part'
421
+
422
+ #Find a value in one column by searching another one (selected by headers or Column objects)
423
+ s.vlookup( 'Part', 'Ref1', &/Type4/ ) #=> "XT3"
424
+ ```
425
+
426
+ Row / Column (Section)
427
+ --------
428
+
429
+ ```ruby
430
+ #Reference a Row or Column
431
+ row = s.row(2)
432
+ col = s.column('B')
433
+
434
+ #Read a value
435
+ row[1] #=> "Type1"
436
+ row['A'] #=> "Type1"
437
+ col[1] #=> "Ref1"
438
+
439
+ #Read multiple values
440
+ row[1..2] #=> ["Type1", "QT1"]
441
+ row[1, 2] #=> ["Type1", "QT1"]
442
+ row['A', 2] #=> ["Type1", "QT1"]
443
+ col[1..2] #=> ["Ref1", "QT1"]
444
+ col[1, 2] #=> ["Ref1", "QT1"]
445
+
446
+ #Write a value
447
+ row[1] = "Type1"
448
+ row['A'] = "Type1"
449
+ col[1] = "Ref1"
450
+
451
+ #Write multiple values
452
+ row[1..2] = "Type1", "QT1"
453
+ row[1, 2] = "Type1", "QT1"
454
+ row['A', 2] = "Type1", "QT1"
455
+ col[1..2] = "Ref1", "QT1"
456
+ col[1, 2] = "Ref1", "QT1"
457
+
458
+ =begin
459
+ Append a value
460
+ Note: Only extends the data boundaries when at the first row or column.
461
+ This allows looping through an entire row or column to append single values,
462
+ without worrying about using the correct index.
463
+ =end
464
+ s.row(1) << 'New'
465
+ s.rows(2) { |r| r << 'Column' }
466
+ s.column(1) << 'New'
467
+ s.columns(2) { |c| c << 'Row' }
468
+
469
+ #Access a cell by column header (Row only)
470
+ s.row(2).cell_by_header( 'Part' ) #=> Cell A2
471
+ s.row(2).cell_h( 'Cost' ) #=> Cell E2
472
+
473
+ #Delete the data referenced by self.
474
+ row.delete
475
+ col.delete
476
+
477
+ #Find the address of a cell matching a block
478
+ row.find { |value| value == 'QT1' }
479
+ row.find &/QT1/
480
+ col.find { |value| value == 'QT1' }
481
+ col.find &/QT1/
482
+
483
+ #Summarise the current row or column into a Hash.
484
+ s.column(1).summarise
485
+ #=> {"Type1"=>3, "Type2"=>2, "Type3"=>1, "Type4"=>1}
486
+
487
+ #Loop through all values
488
+ row.each { |val| puts val }
489
+ col.each { |val| puts val }
490
+
491
+ #Loop through all values without including headers
492
+ col.each_without_headers { |val| puts val }
493
+ col.each_wh { |val| puts val }
494
+
495
+ #Loop through each cell
496
+ row.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
497
+ col.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
498
+
499
+ #Loop through each cell without including headers
500
+ col.each_cell_without_headers { |ce| puts "#{ ce.address }: #{ ce.value }" }
501
+ col.each_cell_wh { |ce| puts "#{ ce.address }: #{ ce.value }" }
502
+
503
+ #Get the letter of a column by its header ( helps build an address when combined with row.idx )
504
+ row.getref( 'Part' ) #=> "A"
505
+
506
+ #Overwrite each value based on its current value
507
+ row.map! { |val| val.to_s + 'a' }
508
+ col.map! { |val| val.to_s + 'a' }
509
+
510
+ #Get the value of a cell in the current row by its header
511
+ row.value_by_header( 'Part' ) #=> 'Type1'
512
+ row.val( 'Part' ) #=> 'Type1'
513
+
514
+ #Set the value of a cell in the current row by its header
515
+ row.set_value_by_header( 'Part', 'Type5' )
516
+ row.set_val( 'Part', 'Type5' )
517
+ ```
518
+
519
+ Cell / Range (Element)
520
+ --------
521
+
522
+ ```ruby
523
+ #Reference a Cell or Range
524
+ #If you select a single-Cell Range you get a Cell
525
+ cell = s.cell( 2, 2 ) #=> Cell
526
+ s.range('B2') #=> Cell
527
+ range = s.range('B2:C3') #=> Range
528
+
529
+ #Get the address and indices of the Element (Indices return that of the first Cell in a Range)
530
+ cell.address
531
+ cell.row
532
+ cell.column
533
+ range.address
534
+ range.row
535
+ range.column
536
+
537
+ #Get and set the value(s)
538
+ cell.value #=> "QT1"
539
+ cell.value = 'QT1'
540
+ range.value #=> [["QT1", "231"], ["QT3", "123"]]
541
+ range.value = "a"
542
+ range.value #=> [["a", "a"], ["a", "a"]]
543
+ range.value = [["QT1", "231"], ["QT3", "123"]]
544
+ range.value #=> [["QT1", "231"], ["QT3", "123"]]
545
+
546
+ #Loop through a range
547
+ range.each { |val| puts val }
548
+
549
+ #Loop through each cell within a range
550
+ range.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
551
+
552
+ ```
553
+
554
+ Address Tools (Included in Sheet, Section, and Element)
555
+ --------
556
+
557
+ ```ruby
558
+ #Get the column index from an address string
559
+ s.address_to_col_index( 'A2' ) #=> 1
560
+
561
+ #Translate an address to indices
562
+ s.address_to_indices( 'A2' ) #=> [ 2, 1 ]
563
+
564
+ #Translate letter(s) to a column index
565
+ s.col_index( 'A' ) #=> 1
566
+
567
+ #Translate a number to column letter(s)
568
+ s.col_letter( 1 ) #=> "A"
569
+
570
+ #Extract the column letter(s) or row number from an address
571
+ s.column_id( 'A2' ) #=> "A"
572
+ s.row_id( 'A2' ) #=> 2
573
+
574
+ #Expand a Range address
575
+ s.expand( 'A1:B2' ) #=> [["A1", "B1"], ["A2","B2"]]
576
+ s.expand( 'A1' ) #=> [["A1"]]
577
+
578
+ #Translate indices to an address
579
+ s.indices_to_address( 2, 1 ) #=> "A2"
580
+
581
+ #Offset an address by rows and columns
582
+ s.offset( 'A2', 1, 2 ) #=> "C3"
583
+ s.offset( 'A2', 2, 0 ) #=> "A4"
584
+ s.offset( 'A2', -1, 0 ) #=> "A1"
585
+
586
+ #Here's a trick you can try if you're going to use the Address tools a lot:
587
+ include RubyExcel::Address
588
+ #Now you can use all the address methods in the current scope:
589
+ offset( 'C3', 1, -2 ) #=> A4
590
+
591
+ ```
592
+
593
+ Importing a Hash
594
+ --------
595
+
596
+ ```ruby
597
+ #Import a nested Hash (useful if you're summarising data before handing it to RubyExcel)
598
+
599
+ #Here's an example Hash (built into the gem as RubyExcel.sample_hash)
600
+ h = {
601
+ Part1: {
602
+ Type1: {
603
+ SubType1: 1, SubType2: 2, SubType3: 3
604
+ },
605
+ Type2: {
606
+ SubType1: 4, SubType2: 5, SubType3: 6
607
+ }
608
+ },
609
+ Part2: {
610
+ Type1: {
611
+ SubType1: 1, SubType2: 2, SubType3: 3
612
+ },
613
+ Type2: {
614
+ SubType1: 4, SubType2: 5, SubType3: 6
615
+ }
616
+ }
617
+ }
618
+
619
+ #Import the Hash to a Sheet
620
+ s.load( h )
621
+ #Or append the Hash to a Sheet
622
+ s << h
623
+
624
+ #Convert the symbols to strings (Not essential, but Excel can't handle Symbols in output)
625
+ s.rows { |r| r.map! { |v| v.is_a?(Symbol) ? v.to_s : v } }
626
+
627
+ #Have a look at the results
628
+ require 'pp'
629
+ pp s.to_a
630
+ [["Part1", "Type1", "SubType1", 1],
631
+ ["Part1", "Type1", "SubType2", 2],
632
+ ["Part1", "Type1", "SubType3", 3],
633
+ ["Part1", "Type2", "SubType1", 4],
634
+ ["Part1", "Type2", "SubType2", 5],
635
+ ["Part1", "Type2", "SubType3", 6],
636
+ ["Part2", "Type1", "SubType1", 1],
637
+ ["Part2", "Type1", "SubType2", 2],
638
+ ["Part2", "Type1", "SubType3", 3],
639
+ ["Part2", "Type2", "SubType1", 4],
640
+ ["Part2", "Type2", "SubType2", 5],
641
+ ["Part2", "Type2", "SubType3", 6]]
642
+
643
+ ```
644
+
645
+ Excel Tools ( requires win32ole and Excel )
646
+ --------
647
+
648
+ Make sure all your data types are compatible with Excel first (see Workbook#to_safe_format!)
649
+
650
+ RubyExcel should only attempt to require win32ole if one of these methods is called.
651
+ This should make it compatible as a data structure on non-windows systems.
652
+
653
+ ```ruby
654
+
655
+ #Sample RubyExcel::Workbook to work with
656
+ rubywb = RubyExcel.sample_sheet.parent
657
+
658
+ #Get a new Excel instance
659
+ excel = rubywb.get_excel
660
+
661
+ #Get a new Excel Workbook
662
+ excelwb = rubywb.get_workbook( excel )
663
+ excelwb = rubywb.get_workbook
664
+
665
+ #Adds a leading single quote to any Strings with a leading Equals sign.
666
+ #This should prevent Excel from throwing Exceptions about invalid data such as "==".
667
+ rubywb.disable_formulas!
668
+
669
+ #Drop data into an Excel Sheet
670
+ #Note: The optional 2nd argument lets you give it an Excel sheet to use
671
+ rubywb.dump_to_sheet( rubywb.sheets(1).to_a )
672
+ rubywb.dump_to_sheet( rubywb.sheets(1).to_a, excelwb.sheets(1) )
673
+
674
+ #Autofit, left-align, and border a WIN32OLE Excel Sheet
675
+ rubywb.make_sheet_pretty( excelwb.sheets(1) )
676
+
677
+ #Output the RubyExcel::Workbook into a new Excel Workbook
678
+ rubywb.to_excel
679
+
680
+ #Output the RubyExcel::Sheet into a new Excel Workbook
681
+ rubywb.sheets(1).to_excel
682
+
683
+ #Output the RubyExcel::Workbook into an Excel Workbook and save the file
684
+ #Note: The default directory is "Documents" or "My Documents" to support Ocra + InnoSetup installs.
685
+ #The Workbook name is used as the filename unless an argument is passed.
686
+ #Note: There is an optional second argument which if set to true doesn't make Excel visible.
687
+ #This is a useful accelerator when running as an automated process.
688
+ #If you set the process to be invisible, don't forget to close Excel after you're finished with it!
689
+ rubywb.save_excel
690
+ rubywb.save_excel( 'Output' )
691
+ rubywb.save_excel( 'c:/example/Output.xlsx' )
692
+ rubywb.save_excel( 'Output', true ) # ( Keeps Excel invisible if creating a new instance )
693
+
694
+ #Convert all internal data to Strings and disable leading equals signs
695
+ #This should ensure compatibility with Excel for most types of data.
696
+ #Note: This will disable any formulas currently in the Workbook.
697
+ #Note: This modifies the data inside the Workbook. For a non-destructive version, leave off the exclamation mark.
698
+ rubywb.to_safe_format!
699
+
700
+ #Add borders to a given Excel Range
701
+ #1st Argument: WIN32OLE Range
702
+ #2nd Argument (default 1), weight of borders (0 to 4)
703
+ #3rd Argument (default false), include inner borders
704
+ RubyExcel.borders( excelwb.sheets(1).usedrange ) #Give used range outer borders
705
+ RubyExcel.borders( excelwb.sheets(1).usedrange, 2, true ) #Give used range inner and outer borders, medium weight
706
+ RubyExcel.borders( excelwb.sheets(1).usedrange, 0, false ) #Clear outer borders from used range
707
+
708
+ #You can even enter formula strings and Excel will evaluate them in the output.
709
+ s = rubywb.sheets(1)
710
+ s.row(1) << 'Formula'
711
+ s.rows(2) { |row| row << "=SUM(D#{ row.idx }:E#{ row.idx })" }
712
+ s.to_excel
713
+
714
+ ```
715
+
716
+ Comparison of operations with and without RubyExcel gem
717
+ --------
718
+
719
+ Without RubyExcel (one way to to it):
720
+
721
+ ```ruby
722
+ #Filter to only 'Part' of 'Type1' and 'Type3' while keeping the header row
723
+ idx = data[0].index( 'Part' )
724
+ data = [ data[0] ] + data[1..-1].select { |row| row[ idx ] =~ /Type[13]/ }
725
+
726
+ #Keep only the columns 'Cost' and 'Ref2' in that order
727
+ max_size = data.max_by(&:length).length #Standardise the row size to transpose into columns
728
+ data.map! { |row| row.length == max_size ? row : row + Array.new( max_size - row.length, nil) }
729
+ headers = [ 'Cost', 'Ref2' ]
730
+ data = data.transpose.select { |header,_| headers.index(header) }.sort_by { |header,_| headers.index(header) }.transpose
731
+
732
+ #Get the combined 'Cost' of every 'Part' of 'Type1' and 'Type3'
733
+ find_idx, sum_idx = data[0].index('Part'), data[0].index('Cost')
734
+ data[1..-1].inject(0) { |sum, row| row[find_idx] =~ /Type[13]/ ? sum + row[sum_idx] : sum }
735
+
736
+ #Write the data to a TSV file
737
+ output = data.map { |row| row.map { |el| "#{el}".strip.gsub( /\s/, ' ' ) }.join "\t" }.join $/
738
+ File.write( 'output.txt', output )
739
+
740
+ #Drop the data into an Excel sheet ( using Excel and win32ole )
741
+ excel = WIN32OLE::new( 'excel.application' )
742
+ excel.visible = true
743
+ wb = excel.workbooks.add
744
+ sheet = wb.sheets(1)
745
+ sheet.range( sheet.cells( 1, 1 ), sheet.cells( data.length, data[0].length ) ).value = data
746
+ #Excel should automatically target the "Documents" / "My Documents" folder when using a new instance.
747
+ #Excel should use the default file extension for the version it's using.
748
+ wb.saveas( 'Output' )
749
+ ```
750
+
751
+ With RubyExcel:
752
+
753
+ ```ruby
754
+ #Filter to only 'Part' of 'Type1' and 'Type3' while keeping the header row
755
+ s.filter!( 'Part', &/Type[13]/ )
756
+
757
+ #Keep only the columns 'Cost' and 'Ref2' in that order
758
+ s.get_columns!( 'Cost', 'Ref2' )
759
+
760
+ #Get the combined 'Cost' of every 'Part' of 'Type1' and 'Type3'
761
+ s.sumif( 'Part', 'Cost', &/Type[13]/ )
762
+
763
+ #Write the data to a TSV file
764
+ File.write( 'output.txt', s.to_s )
765
+
766
+ #Write the data to an Excel file ( requires Excel and win32ole )
767
+ #This aims at "Documents" or "My Documents" by default and uses the Workbook's "name" attribute.
768
+ s.parent.save_excel
769
+ ```
770
+
771
+ Todo List
772
+ =========
773
+
774
+ - Find bugs and extirpate them.
775
+
776
+ - Optimise slow operations
777
+
778
+
779
+ -----------
780
+
781
+ Copyright (c) 2013, Joel Pearson and Contributors. All Rights Reserved.
782
+
780
783
  This project is licenced under the [MIT License](https://github.com/VirtuosoJoel/RubyExcel/blob/master/LICENSE.md).