rubyexcel 0.2.0 → 0.2.1

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: 9a886e1774da95739b10c75471587a8fd5cbcbc2
4
- data.tar.gz: 6cb4e9b43a22f0fe52ff9b240d9163898a5e795d
3
+ metadata.gz: 03433c74ff7536d6bd5bf117a07f8341038e4dc5
4
+ data.tar.gz: 7f3a25254fcf845ebbce3d63c95f7886df99f111
5
5
  SHA512:
6
- metadata.gz: a3567d1778918a2800cd22359f48e8dd67251b26cd20e8ec0e41445a87d6db1ec2dbc33fc9d65b482f1ba3c181e4b682edb42cd4c94b62f4a3f97f0fe5d95717
7
- data.tar.gz: 8edbc74284c3364a2634243dbbcad3e9b663d881e0f59ab9d91eb63fbb3e94ef3845357d9c956657326e6b06d4707434f482262289a4a75e6794daba8d590890
6
+ metadata.gz: 01db3ef75f49654ae1c38cae03eea0989c0dae9a19d7f014615a5d5345198683544c43acb3947990b65f20f2c1bc55b075035f6db301cb1ef1123e131f7717dd
7
+ data.tar.gz: 426e234f27c62efb96cca67f179cab9b1e7484442e263ff2acad8809c1910e77ca71d017c390ac7da45b3bac7d31e9592424fb63a52074cfe317786eac8d01e4
data/lib/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2013 Joel Pearson.
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/lib/README.md ADDED
@@ -0,0 +1,714 @@
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 Excel (XLSX) or TSV format (which Excel can interpret).
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.column( 'A' ) #=> Column
134
+ s.column( 1 ) #=> Column
135
+ ```
136
+
137
+ Using headers to reference the data
138
+ --------
139
+
140
+ Here we're looking for the "Part" in row 7
141
+
142
+ ```ruby
143
+ s.row(7).value_by_header( 'Part' ) #=> "Type2"
144
+ s.row(7).val( 'Part' ) #=> "Type2"
145
+
146
+ s.column_by_header( 'Part' )[7] #=> "Type2"
147
+ s.ch( 'Part' )[7] #=> "Type2"
148
+
149
+ s.row(7).cell_by_header( 'Part' ) #=> Cell A7
150
+ s.row(7).cell_h( 'Part' ) #=> Cell A7
151
+
152
+ s.row(7).getref('Part') #=> "A"
153
+ ```
154
+
155
+ Common Operations
156
+ --------
157
+
158
+ ```ruby
159
+ #Some data to play with
160
+ s = RubyExcel.sample_sheet
161
+
162
+ #Have a look at the data
163
+ puts s
164
+
165
+ #Append a Column by adding a header
166
+ s << 'Number'
167
+
168
+ #Iterate through the rest of the rows while appending data
169
+ x = 1
170
+ s.rows(2) { |row| row << x; x+=1 }
171
+
172
+ #Filter to specific part numbers
173
+ s.filter!( 'Part', &/Type[1-3]/ )
174
+
175
+ #Sort by Part Number
176
+ s.sort_by!( 'Part' )
177
+
178
+ #Add the Number to the Cost in each row.
179
+ s.rows(2) { |row| row.cell_h('Cost').value += row.cell_h('Number').value }
180
+
181
+ #Split the data into multiple sheets by part number
182
+ wb = s.split( 'Part' )
183
+
184
+ #Output a sheet as a TSV file
185
+ File.write( 'Output.txt', wb.sheets(1).to_s )
186
+
187
+ #Output a sheet as an HTML page
188
+ File.write( 'Output.htm', wb.sheets(2).to_html )
189
+
190
+ #Open a sheet in an Excel Workbook
191
+ wb.sheets( 'Type3' ).to_excel
192
+
193
+ ```
194
+
195
+ Workbook
196
+ --------
197
+
198
+ ```ruby
199
+ #Create a workbook
200
+ wb = RubyExcel::Workbook.new
201
+ wb = RubyExcel::Workbook.new( 'My Workbook' )
202
+
203
+ #Get and set the name
204
+ wb.name = 'My Workbook'
205
+ wb.name #=> "My Workbook"
206
+
207
+ #Add sheets to the workbook
208
+ sheet1, sheet2 = wb.add('Sheet1'), wb.add
209
+
210
+ #Delete all sheets from a workbook
211
+ wb.clear_all
212
+
213
+ #Delete a specific sheet
214
+ wb.delete( 1 )
215
+ wb.delete( 'Sheet1' )
216
+ wb.delete( sheet1 )
217
+ wb.delete( /sheet1/i )
218
+
219
+ #Shortcut to create a sheet with a default name and fill it with data
220
+ wb.load( data )
221
+
222
+ #Select a sheet
223
+ wb.sheets(1) #=> RubyExcel::Sheet
224
+ wb.sheets('Sheet1') #=> RubyExcel::Sheet
225
+
226
+ #Iterate through all sheets
227
+ wb.sheets #=> Enumerator
228
+ wb.each #=> Enumerator
229
+
230
+ #Sort the sheets
231
+ wb.sort! { |x,y| x.name <=> y.name }
232
+ wb.sort_by! &:name
233
+
234
+ #Output the workbook as a series of HTML tables
235
+ wb.to_html
236
+ ```
237
+
238
+ Sheet
239
+ --------
240
+
241
+ ```ruby
242
+ #Create a sheet
243
+ s = wb.add #Name defaults to 'Sheet' + total number of sheets
244
+ s = wb.add( 'Sheet1' )
245
+
246
+ #Access the sheet name
247
+ s.name #=> 'Sheet1'
248
+ s.name = 'Sheet1'
249
+
250
+ #Access the parent workbook
251
+ s.workbook
252
+ s.parent
253
+
254
+ #Access the headers
255
+ s.header_rows #=> 1
256
+ s.headers #=> 1
257
+ s.headers = 1
258
+ s.header_rows = 1
259
+
260
+ #Specify the number of header rows when loading data
261
+ s.load( data, 1 )
262
+
263
+ #Append data (at the bottom of the sheet)
264
+ s << data
265
+ s << s
266
+ s += data
267
+ s += s
268
+
269
+ #Remove identical rows in another data set (skipping any headers)
270
+ s -= data
271
+ s -= s
272
+
273
+ #Filter on multiple criteria
274
+ #You can add as many arguments as you like. The order is: Header, Method(Symbol), Argument
275
+ #Note: Returns a copy of the sheet when used without "!".
276
+ #Note: Sheet#filter is simpler to use, this is for the more in-depth stuff.
277
+ _
278
+ #Filter to Part 'Type1' and 'Type3' where Qty is greater than 1
279
+ s.advanced_filter!( 'Part', :=~, /Type[13]/, 'Qty', :>, 1 )
280
+ _
281
+ #Filter to Part 'Type1' where Ref1 includes 'X'
282
+ s.advanced_filter!( 'Part', :==, 'Type1', 'Ref1', :include?, 'X' )
283
+
284
+ #Average all elements in a column by criteria in another column (selected by header)
285
+ #Parameters: Header to pass to the block, Header to average, Block.
286
+ #Note: Accepts Column objects in place of headers.
287
+ s.averageif( 'Part', 'Cost' ) { |part| part == 'Type1' } #=> 56.38333333333333
288
+ s.averageif( 'Part', 'Cost', &/Type1/ ) #=> 56.38333333333333
289
+
290
+ #Select a column by its header
291
+ s.column_by_header( 'Part' )
292
+ s.ch( 'Part' )
293
+ #=> Column
294
+
295
+ #Iterate through rows or columns
296
+ s.rows { |r| puts r } #All rows
297
+ s.rows( 2 ) { |r| puts r } #From the 2nd to the last row
298
+ s.rows( 1, 3 ) { |r| puts r } #Rows 1 to 3
299
+ s.columns { |c| puts c } #All columns
300
+ s.columns( 'B' ) { |c| puts c } #From the 2nd to the last column
301
+ s.columns( 2 ) { |c| puts c } #From the 2nd to the last column
302
+ s.columns( 'B', 'D' ) { |c| puts c } #Columns 2 to 4
303
+ s.columns( 2, 4 ) { |c| puts c } #Columns 2 to 4
304
+
305
+ #Count is included via Enumerable and accepts a block which will yield Row or Column.
306
+ s.rows.count { |r| r.val('Part') == 'Type1' } #=> 3
307
+ s.rows.count { |r| r.val('Part') == 'Type1' && r.val('Qty') < 2 } #=> 2
308
+
309
+ #Remove all empty rows & columns
310
+ s.compact!
311
+
312
+ #Delete the current sheet from the workbook
313
+ s.delete
314
+
315
+ #Delete rows or columns "if( condition )" (iterates in reverse to preserve references during loop)
316
+ s.delete_rows_if { |r| r.empty? }
317
+ s.delete_columns_if { |c| c.empty? }
318
+
319
+ #Filter the data given a column and a block to test values against.
320
+ #Note: Returns a copy of the sheet when used without "!".
321
+ #Note: This gem carries a Regexp to_proc method for Regex shorthand (shown below).
322
+ s.filter!( 'Part' ) { |value| value =~ /Type[13]/ }
323
+ s.filter!( 'Part', &/Type[13]/ )
324
+
325
+ #Filter the data to a specific set of columns by their headers.
326
+ #Note: Returns a copy of the sheet when used without "!".
327
+ s.get_columns!( 'Cost', 'Part', 'Qty' )
328
+ s.gc!( 'Cost', 'Part', 'Qty' )
329
+
330
+ #Insert blank rows or columns ( before, number to insert )
331
+ s.insert_rows( 2, 2 ) #Inserts 2 empty rows before row 2
332
+ s.insert_columns( 'B', 1 ) #Inserts 2 empty columns before column 2
333
+ s.insert_columns( 2, 1 ) #Inserts 2 empty columns before column 2
334
+
335
+ #Find the first row which matches a value within a column (selected by header)
336
+ #Note: Can now accept a Column object in place of a header.
337
+ s.match( 'Qty' ) { |value| value == 1 } #=> 2
338
+ s.match( 'Part', &/Type2/ ) #=> 3
339
+
340
+ #Find the current end of the data range
341
+ s.maxrow #=> 8
342
+ s.rows.count #=> 8
343
+ s.maxcol #=> 5
344
+ s.columns.count #=> 5
345
+
346
+ #Partition the sheet into two, given a header and a block (like Filter)
347
+ #Note: this keeps the headers intact in both output sheets
348
+ type_1_and_3, other = s.partition( 'Part' ) { |value| value =~ /Type[13]/ }
349
+ type_1_and_3, other = s.partition( 'Part', &/Type[13]/ )
350
+
351
+ #Reverse the data by rows or columns (ignores headers)
352
+ s.reverse_rows!
353
+ s.reverse_columns!
354
+
355
+ #Sort the rows by header(s) (ignores header rows)
356
+ s.sort_by!( 'Part' )
357
+ s.sort_by!( 'Qty', 'Part' )
358
+
359
+ #Split a Sheet into a Workbook of Sheets by a column (selected by header)
360
+ wb = s.split( 'Part' )
361
+ #=> <Workbook: [Sheet:Type1, Sheet:Type2, Sheet:Type3, Sheet:Type4]>
362
+
363
+ #Sum all elements in a column by criteria in another column (selected by header)
364
+ #Parameters: Header to pass to the block, Header to sum, Block.
365
+ #Note: Now also accepts Column objects in place of headers.
366
+ s.sumif( 'Part', 'Cost' ) { |part| part == 'Type1' } #=> 169.15
367
+ s.sumif( 'Part', 'Cost', &/Type1/ ) #=> 169.15
368
+
369
+ #Summarise a column by header into a Hash.
370
+ s.summarise( 'Part' )
371
+ #=> {"Type1"=>3, "Type2"=>2, "Type3"=>1, "Type4"=>1}
372
+
373
+ #Convert the data into various formats:
374
+ s.to_a #=> 2D Array
375
+ s.to_excel #=> WIN32OLE Excel Workbook (Contains only the current sheet)
376
+ s.to_html #=> String (HTML table)
377
+ s.to_s #=> String (TSV)
378
+
379
+ #Remove all rows with duplicate values in the given column (selected by header or Column object)
380
+ s.uniq! 'Part'
381
+
382
+ #Find a value in one column by searching another one (selected by headers or Column objects)
383
+ s.vlookup( 'Part', 'Ref1', &/Type4/ ) #=> "XT3"
384
+ ```
385
+
386
+ Row / Column (Section)
387
+ --------
388
+
389
+ ```ruby
390
+ #Reference a Row or Column
391
+ row = s.row(2)
392
+ col = s.column('B')
393
+
394
+ =begin
395
+ Append a value
396
+ Note: Only extends the data boundaries when at the first row or column.
397
+ This allows looping through an entire row or column to append single values,
398
+ without worrying about using the correct index.
399
+ =end
400
+ s.row(1) << 'New'
401
+ s.rows(2) { |r| r << 'Column' }
402
+ s.column(1) << 'New'
403
+ s.columns(2) { |c| c << 'Row' }
404
+
405
+ #Access a cell by column header (Row only)
406
+ s.row(2).cell_by_header( 'Part' ) #=> Cell A2
407
+ s.row(2).cell_h( 'Cost' ) #=> Cell E2
408
+
409
+ #Delete the data referenced by self.
410
+ row.delete
411
+ col.delete
412
+
413
+ #Find the address of a cell matching a block
414
+ row.find { |value| value == 'QT1' }
415
+ row.find &/QT1/
416
+ col.find { |value| value == 'QT1' }
417
+ col.find &/QT1/
418
+
419
+ #Summarise the current row or column into a Hash.
420
+ s.column(1).summarise
421
+ #=> {"Type1"=>3, "Type2"=>2, "Type3"=>1, "Type4"=>1}
422
+
423
+ #Loop through all values
424
+ row.each { |val| puts val }
425
+ col.each { |val| puts val }
426
+
427
+ #Loop through all values without including headers
428
+ col.each_without_headers { |val| puts val }
429
+ col.each_wh { |val| puts val }
430
+
431
+ #Loop through each cell
432
+ row.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
433
+ col.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
434
+
435
+ #Loop through each cell without including headers
436
+ col.each_cell_without_headers { |ce| puts "#{ ce.address }: #{ ce.value }" }
437
+ col.each_cell_wh { |ce| puts "#{ ce.address }: #{ ce.value }" }
438
+
439
+ #Get the letter of a column by its header ( helps build an address when combined with row.idx )
440
+ row.getref( 'Part' ) #=> "A"
441
+
442
+ #Overwrite each value based on its current value
443
+ row.map! { |val| val.to_s + 'a' }
444
+ col.map! { |val| val.to_s + 'a' }
445
+
446
+ #Get the value of a cell in the current row by its header
447
+ row.value_by_header( 'Part' ) #=> 'Type1'
448
+ row.val( 'Part' ) #=> 'Type1'
449
+
450
+ #Set the value of a cell in the current row by its header
451
+ row.set_value_by_header( 'Part', 'Type5' )
452
+ row.set_val( 'Part', 'Type5' )
453
+ ```
454
+
455
+ Cell / Range (Element)
456
+ --------
457
+
458
+ ```ruby
459
+ #Reference a Cell or Range
460
+ #If you select a single-Cell Range you get a Cell
461
+ cell = s.cell( 2, 2 ) #=> Cell
462
+ s.range('B2') #=> Cell
463
+ range = s.range('B2:C3') #=> Range
464
+
465
+ #Get the address and indices of the Element (Indices return that of the first Cell in a Range)
466
+ cell.address
467
+ cell.row
468
+ cell.column
469
+ range.address
470
+ range.row
471
+ range.column
472
+
473
+ #Get and set the value(s)
474
+ cell.value #=> "QT1"
475
+ cell.value = 'QT1'
476
+ range.value #=> [["QT1", "231"], ["QT3", "123"]]
477
+ range.value = "a"
478
+ range.value #=> [["a", "a"], ["a", "a"]]
479
+ range.value = [["QT1", "231"], ["QT3", "123"]]
480
+ range.value #=> [["QT1", "231"], ["QT3", "123"]]
481
+
482
+ #Loop through a range
483
+ range.each { |val| puts val }
484
+
485
+ #Loop through each cell within a range
486
+ range.each_cell { |ce| puts "#{ ce.address }: #{ ce.value }" }
487
+
488
+ ```
489
+
490
+ Address Tools (Included in Sheet, Section, and Element)
491
+ --------
492
+
493
+ ```ruby
494
+ #Get the column index from an address string
495
+ s.address_to_col_index( 'A2' ) #=> 1
496
+
497
+ #Translate an address to indices
498
+ s.address_to_indices( 'A2' ) #=> [ 2, 1 ]
499
+
500
+ #Translate letter(s) to a column index
501
+ s.col_index( 'A' ) #=> 1
502
+
503
+ #Translate a number to column letter(s)
504
+ s.col_letter( 1 ) #=> "A"
505
+
506
+ #Extract the column letter(s) or row number from an address
507
+ s.column_id( 'A2' ) #=> "A"
508
+ s.row_id( 'A2' ) #=> 2
509
+
510
+ #Expand a Range address
511
+ s.expand( 'A1:B2' ) #=> [["A1", "B1"], ["A2","B2"]]
512
+ s.expand( 'A1' ) #=> [["A1"]]
513
+
514
+ #Translate indices to an address
515
+ s.indices_to_address( 2, 1 ) #=> "A2"
516
+
517
+ #Offset an address by rows and columns
518
+ s.offset( 'A2', 1, 2 ) #=> "C3"
519
+ s.offset( 'A2', 2, 0 ) #=> "A4"
520
+ s.offset( 'A2', -1, 0 ) #=> "A1"
521
+
522
+ ```
523
+
524
+ Importing a Hash
525
+ --------
526
+
527
+ ```ruby
528
+ #Import a nested Hash (useful if you're summarising data before handing it to RubyExcel)
529
+
530
+ #Here's an example Hash (built into the gem as RubyExcel.sample_hash)
531
+ h = {
532
+ Part1: {
533
+ Type1: {
534
+ SubType1: 1, SubType2: 2, SubType3: 3
535
+ },
536
+ Type2: {
537
+ SubType1: 4, SubType2: 5, SubType3: 6
538
+ }
539
+ },
540
+ Part2: {
541
+ Type1: {
542
+ SubType1: 1, SubType2: 2, SubType3: 3
543
+ },
544
+ Type2: {
545
+ SubType1: 4, SubType2: 5, SubType3: 6
546
+ }
547
+ }
548
+ }
549
+
550
+ #Import the Hash to a Sheet
551
+ s.load( h )
552
+ #Or append the Hash to a Sheet
553
+ s << h
554
+
555
+ #Convert the symbols to strings (Not essential, but Excel can't handle Symbols in output)
556
+ s.rows { |r| r.map! { |v| v.is_a?(Symbol) ? v.to_s : v } }
557
+
558
+ #Have a look at the results
559
+ require 'pp'
560
+ pp s.to_a
561
+ [["Part1", "Type1", "SubType1", 1],
562
+ ["Part1", "Type1", "SubType2", 2],
563
+ ["Part1", "Type1", "SubType3", 3],
564
+ ["Part1", "Type2", "SubType1", 4],
565
+ ["Part1", "Type2", "SubType2", 5],
566
+ ["Part1", "Type2", "SubType3", 6],
567
+ ["Part2", "Type1", "SubType1", 1],
568
+ ["Part2", "Type1", "SubType2", 2],
569
+ ["Part2", "Type1", "SubType3", 3],
570
+ ["Part2", "Type2", "SubType1", 4],
571
+ ["Part2", "Type2", "SubType2", 5],
572
+ ["Part2", "Type2", "SubType3", 6]]
573
+
574
+ ```
575
+
576
+ Excel Tools ( requires win32ole and Excel )
577
+ --------
578
+
579
+ Make sure all your data types are compatible with Excel first (see Workbook#to_safe_format!)
580
+
581
+ RubyExcel should only attempt to require win32ole if one of these methods is called.
582
+ This should make it compatible as a data structure on non-windows systems.
583
+
584
+ ```ruby
585
+
586
+ #Sample RubyExcel::Workbook to work with
587
+ rubywb = RubyExcel.sample_sheet.parent
588
+
589
+ #Get a new Excel instance
590
+ excel = rubywb.get_excel
591
+
592
+ #Get a new Excel Workbook
593
+ excelwb = rubywb.get_workbook( excel )
594
+ excelwb = rubywb.get_workbook
595
+
596
+ #Adds a leading single quote to any Strings with a leading Equals sign.
597
+ #This should prevent Excel from throwing Exceptions about invalid data such as "==".
598
+ rubywb.disable_formulas!
599
+
600
+ #Drop data into an Excel Sheet
601
+ #Note: The optional 2nd argument lets you give it an Excel sheet to use
602
+ rubywb.dump_to_sheet( rubywb.sheets(1).to_a )
603
+ rubywb.dump_to_sheet( rubywb.sheets(1).to_a, excelwb.sheets(1) )
604
+
605
+ #Autofit, left-align, and border a WIN32OLE Excel Sheet
606
+ rubywb.make_sheet_pretty( excelwb.sheets(1) )
607
+
608
+ #Output the RubyExcel::Workbook into a new Excel Workbook
609
+ rubywb.to_excel
610
+
611
+ #Output the RubyExcel::Sheet into a new Excel Workbook
612
+ rubywb.sheets(1).to_excel
613
+
614
+ #Output the RubyExcel::Workbook into an Excel Workbook and save the file
615
+ #Note: The default directory is "Documents" or "My Documents" to support Ocra + InnoSetup installs.
616
+ #The Workbook name is used as the filename unless an argument is passed.
617
+ #Note: There is an optional second argument which if set to true doesn't make Excel visible.
618
+ #This is a useful accelerator when running as an automated process.
619
+ #If you set the process to be invisible, don't forget to close Excel after you're finished with it!
620
+ rubywb.save_excel
621
+ rubywb.save_excel( 'Output' )
622
+ rubywb.save_excel( 'c:/example/Output.xlsx' )
623
+ rubywb.save_excel( 'Output', true ) # ( Keeps Excel invisible if creating a new instance )
624
+
625
+ #Convert all internal data to Strings and disable leading equals signs
626
+ #This should ensure compatibility with Excel for most types of data.
627
+ #Note: This will disable any formulas currently in the Workbook.
628
+ #Note: This modifies the data inside the Workbook. For a non-destructive version, leave off the exclamation mark.
629
+ rubywb.to_safe_format!
630
+
631
+ #Add borders to a given Excel Range
632
+ #1st Argument: WIN32OLE Range
633
+ #2nd Argument (default 1), weight of borders (0 to 4)
634
+ #3rd Argument (default false), include inner borders
635
+ RubyExcel.borders( excelwb.sheets(1).usedrange ) #Give used range outer borders
636
+ RubyExcel.borders( excelwb.sheets(1).usedrange, 2, true ) #Give used range inner and outer borders, medium weight
637
+ RubyExcel.borders( excelwb.sheets(1).usedrange, 0, false ) #Clear outer borders from used range
638
+
639
+ #You can even enter formula strings and Excel will evaluate them in the output.
640
+ s = rubywb.sheets(1)
641
+ s.row(1) << 'Formula'
642
+ s.rows(2) { |row| row << "=SUM(D#{ row.idx }:E#{ row.idx })" }
643
+ s.to_excel
644
+
645
+ ```
646
+
647
+ Comparison of operations with and without RubyExcel gem
648
+ --------
649
+
650
+ Without RubyExcel (one way to to it):
651
+
652
+ ```ruby
653
+ #Filter to only 'Part' of 'Type1' and 'Type3' while keeping the header row
654
+ idx = data[0].index( 'Part' )
655
+ data = [ data[0] ] + data[1..-1].select { |row| row[ idx ] =~ /Type[13]/ }
656
+
657
+ #Keep only the columns 'Cost' and 'Ref2' in that order
658
+ max_size = data.max_by(&:length).length #Standardise the row size to transpose into columns
659
+ data.map! { |row| row.length == max_size ? row : row + Array.new( max_size - row.length, nil) }
660
+ headers = [ 'Cost', 'Ref2' ]
661
+ data = data.transpose.select { |header,_| headers.index(header) }.sort_by { |header,_| headers.index(header) }.transpose
662
+
663
+ #Get the combined 'Cost' of every 'Part' of 'Type1' and 'Type3'
664
+ find_idx, sum_idx = data[0].index('Part'), data[0].index('Cost')
665
+ data[1..-1].inject(0) { |sum, row| row[find_idx] =~ /Type[13]/ ? sum + row[sum_idx] : sum }
666
+
667
+ #Write the data to a TSV file
668
+ output = data.map { |row| row.map { |el| "#{el}".strip.gsub( /\s/, ' ' ) }.join "\t" }.join $/
669
+ File.write( 'output.txt', output )
670
+
671
+ #Drop the data into an Excel sheet ( using Excel and win32ole )
672
+ excel = WIN32OLE::new( 'excel.application' )
673
+ excel.visible = true
674
+ wb = excel.workbooks.add
675
+ sheet = wb.sheets(1)
676
+ sheet.range( sheet.cells( 1, 1 ), sheet.cells( data.length, data[0].length ) ).value = data
677
+ #Excel should automatically target the "Documents" / "My Documents" folder when using a new instance.
678
+ #Excel should use the default file extension for the version it's using.
679
+ wb.saveas( 'Output' )
680
+ ```
681
+
682
+ With RubyExcel:
683
+
684
+ ```ruby
685
+ #Filter to only 'Part' of 'Type1' and 'Type3' while keeping the header row
686
+ s.filter!( 'Part', &/Type[13]/ )
687
+
688
+ #Keep only the columns 'Cost' and 'Ref2' in that order
689
+ s.get_columns!( 'Cost', 'Ref2' )
690
+
691
+ #Get the combined 'Cost' of every 'Part' of 'Type1' and 'Type3'
692
+ s.sumif( 'Part', 'Cost', &/Type[13]/ )
693
+
694
+ #Write the data to a TSV file
695
+ File.write( 'output.txt', s.to_s )
696
+
697
+ #Write the data to an Excel file ( requires Excel and win32ole )
698
+ #This aims at "Documents" or "My Documents" by default and uses the Workbook's "name" attribute.
699
+ s.parent.save_excel
700
+ ```
701
+
702
+ Todo List
703
+ =========
704
+
705
+ - Find bugs and extirpate them.
706
+
707
+ - Optimise slow operations
708
+
709
+
710
+ -----------
711
+
712
+ Copyright (c) 2013, Joel Pearson. All Rights Reserved.
713
+
714
+ This project is licenced under the [MIT License](LICENSE.md).
data/lib/rubyexcel.rb CHANGED
@@ -33,7 +33,7 @@ module RubyExcel
33
33
  include Enumerable
34
34
 
35
35
  # Names of methods which require win32ole
36
- ExcelToolsMethods = [ :documents_path, :dump_to_sheet, :get_excel, :get_workbook, :make_sheet_pretty, :save_excel, :to_excel, :to_safe_format, :to_safe_format! ]
36
+ ExcelToolsMethods = [ :disable_formulas!, :documents_path, :dump_to_sheet, :get_excel, :get_workbook, :make_sheet_pretty, :save_excel, :to_excel, :to_safe_format, :to_safe_format! ]
37
37
 
38
38
  # Get and set the Workbook name
39
39
  attr_accessor :name
@@ -29,6 +29,20 @@ module RubyExcel
29
29
 
30
30
  class Workbook
31
31
 
32
+
33
+ #
34
+ # Add a single quote before any equals sign in the data.
35
+ # Disables any Strings which would have been interpreted as formulas by Excel
36
+ #
37
+
38
+ def disable_formulas!
39
+ sheets { |s| s.rows { |r| r.each_cell { |ce|
40
+ if ce.value.is_a?( String ) && ce.value[0] == '='
41
+ ce.value = ce.value.sub( /^=/,"'=" )
42
+ end
43
+ } } }; self
44
+ end
45
+
32
46
  #
33
47
  # Find the Windows "Documents" or "My Documents" path, or return the present working directory if it can't be found.
34
48
  #
@@ -153,7 +167,13 @@ module RubyExcel
153
167
  #
154
168
 
155
169
  def to_safe_format!
156
- sheets { |s| s.rows { |r| r.map! { |v| v.to_s.sub( /^=/,"'=" ) } } }; self
170
+ sheets { |s| s.rows { |r| r.map! { |v|
171
+ if v.is_a?( String )
172
+ v[0] == '=' ? v.sub( /^=/,"'=" ) : v
173
+ else
174
+ v.to_s
175
+ end
176
+ } } }; self
157
177
  end
158
178
 
159
179
  end # Workbook
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyexcel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Pearson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-20 00:00:00.000000000 Z
11
+ date: 2013-05-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A tabular data structure in Ruby, with header-based helper methods for
14
14
  analysis and editing, and some of Excel's API style. Can output as 2D Array, Excel,
@@ -26,8 +26,11 @@ files:
26
26
  - lib/rubyexcel/section.rb
27
27
  - lib/rubyexcel/sheet.rb
28
28
  - lib/rubyexcel.rb
29
+ - lib/LICENSE.md
30
+ - lib/README.md
29
31
  homepage: https://github.com/VirtuosoJoel
30
- licenses: []
32
+ licenses:
33
+ - MIT
31
34
  metadata: {}
32
35
  post_install_message:
33
36
  rdoc_options: []