rubyexcel 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/rubyexcel.rb CHANGED
@@ -1,5 +1,14 @@
1
1
  require_relative 'rubyexcel/rubyexcel_components.rb'
2
2
  require_relative 'rubyexcel/excel_tools.rb'
3
+ require 'cgi'
4
+
5
+ #
6
+ # Ruby's standard Regexp class.
7
+ # Regexp#to_proc is a bit of "syntactic sugar" which allows shorthand Regexp blocks
8
+ #
9
+ # @example
10
+ # sheet.filter!( 'Part', &/Type[13]/ )
11
+ #
3
12
 
4
13
  class Regexp
5
14
  def to_proc
@@ -7,93 +16,205 @@ class Regexp
7
16
  end
8
17
  end
9
18
 
19
+ #
20
+ # Namespace for all RubyExcel Classes and Modules
21
+ #
22
+
10
23
  module RubyExcel
11
24
 
25
+ #
26
+ # A Workbook which can hold multiple Sheets
27
+ #
28
+
12
29
  class Workbook
30
+ include Enumerable
13
31
 
32
+ #
33
+ # Creates a RubyExcel::Workbook instance.
34
+ #
35
+
14
36
  def initialize
15
37
  @sheets = []
16
38
  end
17
39
 
40
+ #
41
+ # Appends an object to the Workbook
42
+ #
43
+ # @param [RubyExcel::Workbook, RubyExcel::Sheet, Array<Array>] other the object to append to the Workbook
44
+ #
45
+
18
46
  def <<( other )
19
47
  case other
20
48
  when Workbook ; other.each { |sht| sht.workbook = self; @sheets << sht }
21
- when Sheet ; @sheets << other; other.workbook = self
22
- when Array ; @sheets << add.load( other )
23
- else ; fail TypeError, "Unsupported Type: #{ other.class }"
49
+ when Sheet ; @sheets << other; other.workbook = self
50
+ when Array ; @sheets << add.load( other )
51
+ else ; fail TypeError, "Unsupported Type: #{ other.class }"
24
52
  end
25
53
  self
26
54
  end
27
55
 
56
+ #
57
+ # Adds a Sheet to the Workbook.
58
+ # If no argument is given, names the Sheet 'Sheet' + total number of Sheets
59
+ #
60
+ # @example
61
+ # sheet = workbook.add
62
+ # #=> RubyExcel::Sheet:0x2b3a0b8: Sheet1
63
+ #
64
+ # @param [nil, RubyExcel::Sheet, String] ref the identifier or Sheet to add
65
+ # @return [RubyExcel::Sheet] the Sheet which was added
66
+
28
67
  def add( ref=nil )
29
68
  case ref
30
- when nil ; s = Sheet.new( 'Sheet' + ( @sheets.count + 1 ).to_s, self )
31
- when Sheet ; ( s = ref ).workbook = self
69
+ when nil ; s = Sheet.new( 'Sheet' + ( @sheets.count + 1 ).to_s, self )
70
+ when Sheet ; ( s = ref ).workbook = self
32
71
  when String ; s = Sheet.new( ref, self )
33
- else ; fail TypeError, "Unsupported Type: #{ ref.class }"
72
+ else ; fail TypeError, "Unsupported Type: #{ ref.class }"
34
73
  end
35
- @sheets << s; s
74
+ @sheets << s
75
+ s
36
76
  end
37
77
  alias add_sheet add
38
78
 
79
+ #
80
+ # Removes all Sheets from the Workbook
81
+ #
82
+
39
83
  def clear_all
40
84
  @sheets = []; self
41
85
  end
86
+ alias delete_all clear_all
87
+
88
+ #
89
+ # Removes Sheet(s) from the Workbook
90
+ #
91
+ # @param [Fixnum, String, Regexp, RubyExcel::Sheet] ref the reference or object to remove
92
+ #
42
93
 
43
94
  def delete( ref )
44
95
  case ref
45
96
  when Fixnum ; @sheets.delete_at( ref - 1 )
46
97
  when String ; @sheets.reject! { |s| s.name == ref }
47
98
  when Regexp ; @sheets.reject! { |s| s.name =~ ref }
48
- when Sheet ; @sheets.reject! { |s| s == ref }
49
- else ; fail ArgumentError, "Unrecognised Argument Type: #{ ref.class }"
50
- end ; self
99
+ when Sheet ; @sheets.reject! { |s| s == ref }
100
+ else ; fail ArgumentError, 'Unrecognised Argument Type: ' + ref.class.to_s
101
+ end
102
+ self
51
103
  end
52
104
 
105
+ #
106
+ # Return a copy of self
107
+ #
108
+ # @return [RubyExcel::Workbook]
109
+ #
110
+
53
111
  def dup
54
112
  wb = Workbook.new
55
113
  self.each { |s| wb.add s.dup }
56
114
  wb
57
115
  end
58
116
 
117
+ #
118
+ # Check whether the workbook has Sheets
119
+ #
120
+ # @return [Boolean] if there are any Sheets in the Workbook
121
+ #
122
+
59
123
  def empty?
60
124
  @sheets.empty?
61
125
  end
62
126
 
127
+ # @overload load( input_data, header_rows=1 )
128
+ # Shortcut to create a Sheet and fill it with data
129
+ # @param [Array<Array>, Hash<Hash>] input_data the data to fill the Sheet with
130
+ # @param Fixnum] header_rows the number of Rows to be treated as headers
131
+ #
132
+
63
133
  def load( *args )
64
134
  add.load( *args )
65
135
  end
136
+
137
+ #
138
+ # Select a Sheet or iterate through them
139
+ #
140
+ # @param [Fixnum, String, nil] ref the reference to select a Sheet by
141
+ # @return [RubyExcel::Sheet] if a search term was given
142
+ # @return [Enumerator] if nil or no argument given
143
+ #
66
144
 
67
145
  def sheets( ref=nil )
68
146
  return to_enum (:each) if ref.nil?
69
147
  ref.is_a?( Fixnum ) ? @sheets[ ref - 1 ] : @sheets.find { |s| s.name =~ /^#{ ref }$/i }
70
148
  end
71
149
 
72
- def sort!
73
- @sheets = @sheets.sort(&block)
150
+ # {Workbook#sort!}
151
+
152
+ def sort( &block )
153
+ dup.sort!( &block )
74
154
  end
75
155
 
156
+ #
157
+ # Sort Sheets according to a block
158
+ #
159
+
160
+ def sort!( &block )
161
+ @sheets = @sheets.sort( &block )
162
+ end
163
+
164
+ # {Workbook#sort_by!}
165
+
166
+ def sort_by( &block )
167
+ dup.sort_by!( &block )
168
+ end
169
+
170
+ #
171
+ # Sort Sheets by an attribute given in a block
172
+ #
173
+
76
174
  def sort_by!( &block )
77
- @sheets = @sheets.sort_by(&block)
175
+ @sheets = @sheets.sort_by( &block )
78
176
  end
79
177
 
80
- include Enumerable
178
+ #
179
+ # Yields each Sheet.
180
+ #
81
181
 
82
182
  def each
83
- return to_enum(:each) unless block_given?
183
+ return to_enum( :each ) unless block_given?
84
184
  @sheets.each { |s| yield s }
85
185
  end
86
186
 
87
- end
187
+ end # Workbook
188
+
189
+ #
190
+ # The front-end class for data manipulation and output.
191
+ #
88
192
 
89
193
  class Sheet
194
+ include Address
195
+ include Enumerable
90
196
 
197
+ # The Data underlying the Sheet
91
198
  attr_reader :data
92
- attr_accessor :name, :header_rows, :workbook
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
+
93
209
  alias parent workbook; alias parent= workbook=
94
- alias headers header_rows; alias headers= header_rows=
210
+ alias headers header_rows; alias headers= header_rows=
95
211
 
96
- include Address
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
+ #
97
218
 
98
219
  def initialize( name, workbook )
99
220
  @workbook = workbook
@@ -101,72 +222,182 @@ module RubyExcel
101
222
  @header_rows = nil
102
223
  @data = Data.new( self, [[]] )
103
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
+ #
104
239
 
105
240
  def[]( addr )
106
241
  range( addr ).value
107
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
+ #
108
255
 
109
256
  def []=( addr, val )
110
257
  range( addr ).value = val
111
258
  end
112
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
+ #
113
266
 
114
267
  def +( other )
115
268
  dup << other
116
269
  end
117
270
 
271
+ #
272
+ # Subtract data from the Sheet
273
+ #
274
+ # @param [Array<Array>, RubyExcel::Sheet] other the data to subtract
275
+ # @return [RubyExcel::Sheet] returns a new Sheet
276
+ #
277
+
118
278
  def -( other )
119
279
  case other
120
280
  when Array ; Workbook.new.load( data.all - other )
121
281
  when Sheet ; Workbook.new.load( data.all - other.data.no_headers )
122
- else ; fail ArgumentError, "Unsupported class: #{ other.class }"
282
+ else ; fail ArgumentError, "Unsupported class: #{ other.class }"
123
283
  end
124
284
  end
125
285
 
286
+ #
287
+ # Append data to the Sheet
288
+ #
289
+ # @param [Array<Array>, Hash<Hash>, RubyExcel::Sheet] other the data to append
290
+ # @return [self]
291
+ #
292
+
126
293
  def <<( other )
127
294
  case other
128
295
  when Array ; load( data.all + other, header_rows )
129
- when Hash ; load( data.all + _convert_hash( other ) )
296
+ when Hash ; load( data.all + _convert_hash( other ) )
130
297
  when Sheet ; load( data.all + other.data.no_headers, header_rows )
131
- else ; fail ArgumentError, "Unsupported class: #{ other.class }"
298
+ else ; fail ArgumentError, "Unsupported class: #{ other.class }"
132
299
  end
300
+ self
133
301
  end
134
302
 
303
+ #
304
+ # Access an Element by indices.
305
+ #
306
+ # @param [Fixnum] row the row index
307
+ # @param [Fixnum] col the column index
308
+ # @return [RubyExcel::Element]
309
+ # @note Indexing is 1-based like Excel VBA
310
+ #
311
+
135
312
  def cell( row, col )
136
313
  Element.new( self, indices_to_address( row, col ) )
137
314
  end
138
315
  alias cells cell
139
316
 
317
+ #
318
+ # Access a Column (Section) by its reference.
319
+ #
320
+ # @param [String, Fixnum] index the Column reference
321
+ # @return [RubyExcel::Column]
322
+ # @note Index 'A' and 1 both select the 1st Column
323
+ #
324
+
140
325
  def column( index )
141
326
  Column.new( self, col_letter( index ) )
142
327
  end
143
328
 
329
+ #
330
+ # Access a Column (Section) by its header.
331
+ #
332
+ # @param [String] header the Column header
333
+ # @return [RubyExcel::Column]
334
+ #
335
+
144
336
  def column_by_header( header )
145
- Column.new( self, data.colref_by_header( header ) )
337
+ header.is_a?( Column ) ? header : Column.new( self, data.colref_by_header( header ) )
146
338
  end
147
339
  alias ch column_by_header
148
340
 
341
+ #
342
+ # Yields each Column to the block
343
+ #
344
+ # @param [String, Fixnum] start_column the Column to start looping from
345
+ # @param [String, Fixnum] end_column the Column to end the loop at
346
+ # @note Iterates to the last Column in the Sheet unless given a second argument.
347
+ #
348
+
149
349
  def columns( start_column = 'A', end_column = data.cols )
150
- return to_enum(:columns, start_column, end_column) unless block_given?
151
- ( col_letter( start_column )..col_letter( end_column ) ).each { |idx| yield column( idx ) }; self
350
+ return to_enum( :columns, start_column, end_column ) unless block_given?
351
+ ( col_letter( start_column )..col_letter( end_column ) ).each { |idx| yield column( idx ) }
352
+ self
152
353
  end
153
354
 
355
+ # {Sheet#compact!}
356
+
357
+ def compact
358
+ dup.compact!
359
+ end
360
+
361
+ #
362
+ # Removes empty Columns and Rows
363
+ #
364
+
154
365
  def compact!
155
366
  data.compact!; self
156
367
  end
157
368
 
369
+ #
370
+ # Removes Sheet from the parent Workbook
371
+ #
372
+
158
373
  def delete
159
374
  workbook.delete self
160
375
  end
161
376
 
377
+ #
378
+ # Deletes each Row where the block is true
379
+ #
380
+
162
381
  def delete_rows_if
382
+ return to_enum( :delete_rows_if ) unless block_given?
163
383
  rows.reverse_each { |r| r.delete if yield r }; self
164
384
  end
165
385
 
386
+ #
387
+ # Deletes each Column where the block is true
388
+ #
389
+
166
390
  def delete_columns_if
391
+ return to_enum( :delete_columns_if ) unless block_given?
167
392
  columns.reverse_each { |c| c.delete if yield c }; self
168
393
  end
169
394
 
395
+ #
396
+ # Return a copy of self
397
+ #
398
+ # @return [RubyExcel::Sheet]
399
+ #
400
+
170
401
  def dup
171
402
  s = Sheet.new( name, workbook )
172
403
  d = data
@@ -178,40 +409,89 @@ module RubyExcel
178
409
  s
179
410
  end
180
411
 
412
+ #
413
+ # Check whether the Sheet contains data
414
+ #
415
+ # @return [Boolean] if there is any data
416
+ #
417
+
181
418
  def empty?
182
419
  data.empty?
183
420
  end
184
421
 
185
- def filter( ref, &block )
186
- dup.filter!( ref, &block )
422
+ # {Sheet#filter!}
423
+
424
+ def filter( header, &block )
425
+ dup.filter!( header, &block )
187
426
  end
188
427
 
189
- def filter!( ref, &block )
190
- data.filter!( ref, &block ); self
428
+ #
429
+ # Removes all Rows (omitting headers) where the block is false
430
+ #
431
+ # @param [String] header the header of the Column to pass to the block
432
+ # @yield [Object] the value at the intersection of Column and Row
433
+ # @return [self]
434
+ #
435
+ def filter!( header, &block )
436
+ data.filter!( header, &block ); self
191
437
  end
192
438
 
439
+ # {Sheet#get_columns!}
440
+
193
441
  def get_columns( *headers )
194
442
  dup.get_columns!( *headers )
195
443
  end
196
444
  alias gc get_columns
197
445
 
446
+ #
447
+ # Select and re-order Columns by a list of headers
448
+ #
449
+ # @param [Array<String>] headers the ordered list of headers to keep
450
+ # @note Invalid headers will be skipped
451
+ #
452
+
198
453
  def get_columns!( *headers )
199
454
  data.get_columns!( *headers ); self
200
455
  end
201
456
  alias gc! get_columns!
202
457
 
458
+ # @overload insert_columns( before, number=1 )
459
+ # Insert blank Columns into the data
460
+ #
461
+ # @param [String, Fixnum] before the Column reference to insert before.
462
+ # @param [Fixnum] number the number of new Columns to insert
463
+ #
464
+
203
465
  def insert_columns( *args )
204
466
  data.insert_columns( *args ); self
205
467
  end
206
468
 
469
+ # @overload insert_rows( before, number=1 )
470
+ # Insert blank Rows into the data
471
+ #
472
+ # @param [Fixnum] before the Row index to insert before.
473
+ # @param [Fixnum] number the number of new Rows to insert
474
+ #
475
+
207
476
  def insert_rows( *args )
208
477
  data.insert_rows( *args ); self
209
478
  end
210
479
 
480
+ #
481
+ # View the object for debugging
482
+ #
483
+
211
484
  def inspect
212
485
  "#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ name }"
213
486
  end
214
487
 
488
+ #
489
+ # Populate the Sheet with data (overwrite)
490
+ #
491
+ # @param [Array<Array>, Hash<Hash>] input_data the data to fill the Sheet with
492
+ # @param header_rows [Fixnum] the number of Rows to be treated as headers
493
+ #
494
+
215
495
  def load( input_data, header_rows=1 )
216
496
  input_data = _convert_hash(input_data) if input_data.is_a?(Hash)
217
497
  input_data.is_a?(Array) or fail ArgumentError, 'Input must be an Array or Hash'
@@ -219,70 +499,206 @@ module RubyExcel
219
499
  @data = Data.new( self, input_data ); self
220
500
  end
221
501
 
502
+ #
503
+ # Find the row number by looking up a value in a Column
504
+ #
505
+ # @param [String] header the header of the Column to pass to the block
506
+ # @yield yields each value in the Column to the block
507
+ # @return [Fixnum, nil] the row number of the first match or nil if nothing is found
508
+ #
509
+
222
510
  def match( header, &block )
223
511
  row_id( column_by_header( header ).find( &block ) )
224
512
  end
225
513
 
514
+ #
515
+ # The highest currently used row number
516
+ #
517
+
226
518
  def maxrow
227
519
  data.rows
228
520
  end
229
521
 
522
+ #
523
+ # The highest currently used column number
524
+ #
525
+
230
526
  def maxcol
231
527
  data.cols
232
528
  end
233
529
  alias maxcolumn maxcol
234
530
 
531
+ #
532
+ # Split the Sheet into two Sheets by evaluating each value in a column
533
+ #
534
+ # @param [String] header the header of the Column which contains the yield value
535
+ # @yield [value] yields the value of each row under the given header
536
+ # @return [Array<RubyExcel::Sheet, RubyExcel::Sheet>] Two Sheets: true and false. Headers included.
537
+ #
538
+
539
+ def partition( header, &block )
540
+ data.partition( header, &block ).map { |d| dup.load( d ) }
541
+ end
542
+
543
+ #
544
+ # Access an Element by address.
545
+ #
546
+ # @param [String, Element] first_cell the first Cell or Address in the Range
547
+ # @param [String, Element] last_cell the last Cell or Address in the Range
548
+ # @return [RubyExcel::Element]
549
+ # @note These are all valid arguments:
550
+ # ('A1')
551
+ # ('A1:B2')
552
+ # ('A1', 'B2')
553
+ # (cell1)
554
+ # (cell1, cell2)
555
+ #
556
+
235
557
  def range( first_cell, last_cell=nil )
236
558
  Element.new( self, to_range_address( first_cell, last_cell ) )
237
559
  end
238
560
 
561
+ #
562
+ # Reverse the Sheet Columns
563
+ #
564
+
239
565
  def reverse_columns!
240
566
  data.reverse_columns!
241
567
  end
242
568
 
569
+ #
570
+ # Reverse the Sheet Rows (without affecting the headers)
571
+ #
572
+
243
573
  def reverse_rows!
244
574
  data.reverse_rows!
245
575
  end
576
+
577
+ #
578
+ # Create a Row from an index
579
+ #
580
+ # @param [Fixnum] index the Row index
581
+ # @return [RubyExcel::Row]
582
+ #
246
583
 
247
584
  def row( index )
248
585
  Row.new( self, index )
249
586
  end
250
587
 
588
+ #
589
+ # Yields each Row to the block
590
+ #
591
+ # @param [Fixnum] start_row the Row to start looping from
592
+ # @param [Fixnum] end_row the Row to end the loop at
593
+ # @note Iterates to the last Row in the Sheet unless given a second argument.
594
+ #
595
+
251
596
  def rows( start_row = 1, end_row = data.rows )
252
597
  return to_enum(:rows, start_row, end_row) unless block_given?
253
598
  ( start_row..end_row ).each { |idx| yield row( idx ) }; self
254
599
  end
600
+ alias each rows
601
+
602
+ # {Sheet#sort!}
603
+
604
+ def sort( &block )
605
+ dup.sort!( &block )
606
+ end
607
+
608
+ #
609
+ # Sort the data according to a block (avoiding headers)
610
+ #
255
611
 
256
612
  def sort!( &block )
257
613
  data.sort!( &block ); self
258
614
  end
259
615
 
616
+ # {Sheet#sort_by!}
617
+
618
+ def sort_by( &block )
619
+ dup.sort_by!( &block )
620
+ end
621
+
622
+ #
623
+ # Sort the data by the block value (avoiding headers)
624
+ #
625
+
260
626
  def sort_by!( &block )
261
627
  data.sort_by!( &block ); self
262
628
  end
263
629
 
630
+ #
631
+ # Sum the values in a Column by searching another Column
632
+ #
633
+ # @param [String] find_header the header of the Column to yield to the block
634
+ # @param [String] sum_header the header of the Column to sum
635
+ # @yield yields the find_header column values to the block
636
+ #
637
+
264
638
  def sumif( find_header, sum_header )
639
+ return to_enum( :sumif ) unless block_given?
265
640
  find_col, sum_col = ch( find_header ), ch( sum_header )
266
641
  find_col.each_cell.inject(0) { |sum,ce| yield( ce.value ) && ce.row > header_rows ? sum + sum_col[ ce.row ] : sum }
267
642
  end
268
643
 
644
+ #
645
+ # The Sheet as a 2D Array
646
+ #
647
+
269
648
  def to_a
270
649
  data.all
271
650
  end
272
651
 
652
+ #
653
+ # The Sheet as a WIN32OLE Excel Workbook
654
+ # @note This requires Windows and MS Excel
655
+ #
656
+
273
657
  def to_excel
274
658
  workbook.dup.clear_all.add( self.dup ).workbook.to_excel
275
659
  end
276
660
 
661
+ #
662
+ # The Sheet as a String containing an HTML Table
663
+ #
664
+
665
+ def to_html
666
+ "<table>\n" + data.map { |row| '<tr>' + row.map { |v| '<td>' + CGI.escapeHTML(v.to_s) }.join() + "\n" }.join() + '</table>'
667
+ end
668
+
669
+ #
670
+ # The Sheet as a Tab Seperated Value String
671
+ #
672
+
277
673
  def to_s
278
- data.nil? ? '' : data.map { |ar| ar.map { |v| v.to_s.gsub(/\t|\n/,' ') }.join "\t" }.join( $/ )
674
+ data.map { |ar| ar.map { |v| v.to_s.gsub(/\t|\n/,' ') }.join "\t" }.join( $/ )
279
675
  end
280
676
 
677
+ # {Sheet#uniq!}
678
+
679
+ def uniq( header )
680
+ dup.uniq!( header )
681
+ end
682
+
683
+ #
684
+ # Remove any Rows with duplicate values within a Column
685
+ #
686
+ # @param [String] header the header of the Column to check for duplicates
687
+ #
688
+
281
689
  def uniq!( header )
282
690
  data.uniq!( header ); self
283
691
  end
284
692
  alias unique! uniq!
285
693
 
694
+ #
695
+ # Find a value within a Column by searching another Column
696
+ #
697
+ # @param [String] find_header the header of the Column to search
698
+ # @param [String] return_header the header of the return value Column
699
+ # @yield the first matching value
700
+ #
701
+
286
702
  def vlookup( find_header, return_header, &block )
287
703
  find_col, return_col = ch( find_header ), ch( return_header )
288
704
  return_col[ row_id( find_col.find( &block ) ) ] rescue nil
@@ -298,6 +714,6 @@ module RubyExcel
298
714
  _hash_to_a(h).each_slice(2).map { |a1,a2| a1 << a2.last }
299
715
  end
300
716
 
301
- end
717
+ end # Sheet
302
718
 
303
- end
719
+ end # RubyExcel