rubyexcel 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rubyexcel/address.rb +88 -5
- data/lib/rubyexcel/data.rb +209 -21
- data/lib/rubyexcel/element.rb +67 -6
- data/lib/rubyexcel/excel_tools.rb +62 -3
- data/lib/rubyexcel/rubyexcel_components.rb +8 -0
- data/lib/rubyexcel/section.rb +150 -14
- data/lib/rubyexcel.rb +448 -32
- metadata +2 -2
@@ -1,10 +1,21 @@
|
|
1
1
|
require 'win32ole' #Interface with Excel
|
2
2
|
require 'win32/registry' #Find Documents / My Documents for default directory
|
3
3
|
|
4
|
+
# Holder for WIN32OLE Excel Constants
|
4
5
|
module ExcelConstants; end
|
5
6
|
|
6
7
|
module RubyExcel
|
7
8
|
|
9
|
+
#
|
10
|
+
# Add borders to an Excel Range
|
11
|
+
#
|
12
|
+
# @param [WIN32OLE::Range] range the Excel Range to add borders to
|
13
|
+
# @param [Fixnum] weight the weight of the borders
|
14
|
+
# @param [Boolean] inner add inner borders
|
15
|
+
# @raise [ArgumentError] 'First Argument must be WIN32OLE Range'
|
16
|
+
# @return [WIN32OLE::Range] the range initially given
|
17
|
+
#
|
18
|
+
|
8
19
|
def self.borders( range, weight=1, inner=false )
|
9
20
|
range.ole_respond_to?( :borders ) or fail ArgumentError, 'First Argument must be WIN32OLE Range'
|
10
21
|
[0,1,2,3].include?( weight ) or fail ArgumentError, "Invalid line weight #{ weight }. Must be from 0 to 3"
|
@@ -18,12 +29,27 @@ module RubyExcel
|
|
18
29
|
|
19
30
|
class Workbook
|
20
31
|
|
32
|
+
#
|
33
|
+
# Drop a multidimensional Array into an Excel Sheet
|
34
|
+
#
|
35
|
+
# @param [Array<Array>] data the data to place in the Sheet
|
36
|
+
# @param [WIN32OLE::Worksheet, nil] sheet optional WIN32OLE Worksheet to use
|
37
|
+
# @return [WIN32OLE::Worksheet] the Worksheet containing the data
|
38
|
+
#
|
39
|
+
|
21
40
|
def dump_to_sheet( data, sheet=nil )
|
22
41
|
data.is_a?( Array ) or fail ArgumentError, "Invalid data type: #{ data.class }"
|
23
42
|
sheet ||= get_workbook.sheets(1)
|
24
43
|
sheet.range( sheet.cells( 1, 1 ), sheet.cells( data.length, data[0].length ) ).value = data
|
25
44
|
sheet
|
26
45
|
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Open or connect to an Excel instance
|
49
|
+
#
|
50
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
51
|
+
# @return [WIN32OLE::Excel] the first available Excel application
|
52
|
+
#
|
27
53
|
|
28
54
|
def get_excel( invisible = false )
|
29
55
|
excel = WIN32OLE::connect( 'excel.application' ) rescue WIN32OLE::new( 'excel.application' )
|
@@ -31,6 +57,14 @@ module RubyExcel
|
|
31
57
|
excel
|
32
58
|
end
|
33
59
|
|
60
|
+
#
|
61
|
+
# Create a new Excel Workbook
|
62
|
+
#
|
63
|
+
# @param [WIN32OLE::Excel, nil] excel an Excel object to use
|
64
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
65
|
+
# @return [WIN32OLE::Workbook] the new Excel Workbook
|
66
|
+
#
|
67
|
+
|
34
68
|
def get_workbook( excel=nil, invisible = false )
|
35
69
|
excel ||= get_excel( invisible )
|
36
70
|
wb = excel.workbooks.add
|
@@ -38,15 +72,32 @@ module RubyExcel
|
|
38
72
|
wb
|
39
73
|
end
|
40
74
|
|
75
|
+
#
|
76
|
+
# Take an Excel Sheet and standardise some of the formatting
|
77
|
+
#
|
78
|
+
# @param [WIN32OLE::Worksheet] sheet the Sheet to add formatting to
|
79
|
+
# @return [WIN32OLE::Worksheet] the sheet with formatting added
|
80
|
+
#
|
81
|
+
|
41
82
|
def make_sheet_pretty( sheet )
|
42
83
|
c = sheet.cells
|
43
84
|
c.rowheight = 15
|
44
85
|
c.entireColumn.autoFit
|
45
86
|
c.horizontalAlignment = -4108
|
46
87
|
c.verticalAlignment = -4108
|
88
|
+
sheet.UsedRange.Columns.each { |col| col.ColumnWidth = 30 if col.ColumnWidth > 50 }
|
89
|
+
RubyExcel.borders( sheet.usedrange, 1, true )
|
47
90
|
sheet
|
48
91
|
end
|
49
92
|
|
93
|
+
#
|
94
|
+
# Save the RubyExcel::Workbook as an Excel Workbook
|
95
|
+
#
|
96
|
+
# @param [String] filename the filename to save as
|
97
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
98
|
+
# @return [WIN32OLE::Workbook] the Workbook, saved as filename.
|
99
|
+
#
|
100
|
+
|
50
101
|
def save_excel( filename = 'Output', invisible = false )
|
51
102
|
filename = filename.gsub('/','\\')
|
52
103
|
unless filename.include?('\\')
|
@@ -59,9 +110,16 @@ module RubyExcel
|
|
59
110
|
wb
|
60
111
|
end
|
61
112
|
|
113
|
+
#
|
114
|
+
# Output the RubyExcel::Workbook to Excel
|
115
|
+
#
|
116
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
117
|
+
# @return [WIN32OLE::Workbook] the Workbook in Excel
|
118
|
+
#
|
119
|
+
|
62
120
|
def to_excel( invisible = false )
|
63
121
|
self.sheets.count == self.sheets.map(&:name).uniq.length or fail NoMethodError, 'Duplicate sheet name'
|
64
|
-
wb = get_workbook( nil,
|
122
|
+
wb = get_workbook( nil, true )
|
65
123
|
wb.parent.displayAlerts = false
|
66
124
|
first_time = true
|
67
125
|
self.each do |s|
|
@@ -70,9 +128,10 @@ module RubyExcel
|
|
70
128
|
make_sheet_pretty( dump_to_sheet( s.data.all, sht ) )
|
71
129
|
end
|
72
130
|
wb.sheets(1).select
|
131
|
+
wb.application.visible = true unless invisible
|
73
132
|
wb
|
74
133
|
end
|
75
134
|
|
76
|
-
end
|
135
|
+
end # Workbook
|
77
136
|
|
78
|
-
end
|
137
|
+
end # RubyExcel
|
@@ -5,6 +5,10 @@ require_relative 'section.rb'
|
|
5
5
|
|
6
6
|
module RubyExcel
|
7
7
|
|
8
|
+
#
|
9
|
+
# Example data to use in tests / demos
|
10
|
+
#
|
11
|
+
|
8
12
|
def self.sample_data
|
9
13
|
[
|
10
14
|
[ 'Part', 'Ref1', 'Ref2', 'Qty', 'Cost' ],
|
@@ -18,6 +22,10 @@ module RubyExcel
|
|
18
22
|
]
|
19
23
|
end
|
20
24
|
|
25
|
+
#
|
26
|
+
# Shortcut to create a Sheet with example data
|
27
|
+
#
|
28
|
+
|
21
29
|
def self.sample_sheet
|
22
30
|
Workbook.new.load RubyExcel.sample_data
|
23
31
|
end
|
data/lib/rubyexcel/section.rb
CHANGED
@@ -1,91 +1,177 @@
|
|
1
1
|
module RubyExcel
|
2
2
|
|
3
|
+
#
|
4
|
+
# Superclass for Row and Column
|
5
|
+
#
|
6
|
+
|
3
7
|
class Section
|
4
|
-
|
5
8
|
include Address
|
9
|
+
include Enumerable
|
6
10
|
|
7
|
-
|
11
|
+
# The Sheet parent of the Section
|
12
|
+
attr_reader :sheet
|
8
13
|
alias parent sheet
|
9
14
|
|
15
|
+
# The Data underlying the Sheet
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
#
|
19
|
+
# Creates a RubyExcel::Section instance
|
20
|
+
#
|
21
|
+
# @param [RubyExcel::Sheet] sheet the parent Sheet
|
22
|
+
#
|
23
|
+
|
10
24
|
def initialize( sheet )
|
11
25
|
@sheet = sheet
|
12
26
|
@data = sheet.data
|
13
27
|
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Append a value to the Section.
|
31
|
+
# This only adds an extra cell if it is the first Row / Column.
|
32
|
+
# This prevents a loop through Rows or Columns from extending diagonally away from the main data.
|
33
|
+
#
|
34
|
+
# @param [Object] value the object to append
|
35
|
+
#
|
14
36
|
|
15
37
|
def <<( value )
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
lastone = ( col_index( idx ) == 1 ? data.rows + 1 : data.rows )
|
38
|
+
case self
|
39
|
+
when Row ; lastone = ( col_index( idx ) == 1 ? data.cols + 1 : data.cols )
|
40
|
+
else ; lastone = ( col_index( idx ) == 1 ? data.rows + 1 : data.rows )
|
20
41
|
end
|
21
42
|
data[ translate_address( lastone ) ] = value
|
22
43
|
end
|
23
44
|
|
45
|
+
#
|
46
|
+
# Access a cell by its index within the Section
|
47
|
+
#
|
48
|
+
|
24
49
|
def cell( ref )
|
25
50
|
Element.new( sheet, translate_address( ref ) )
|
26
51
|
end
|
27
52
|
|
53
|
+
#
|
54
|
+
# Delete the data referenced by self
|
55
|
+
#
|
56
|
+
|
28
57
|
def delete
|
29
58
|
data.delete( self )
|
30
59
|
end
|
31
60
|
|
61
|
+
#
|
62
|
+
# Check whether the data in self is empty
|
63
|
+
#
|
64
|
+
|
32
65
|
def empty?
|
33
66
|
all? { |val| val.to_s.empty? }
|
34
67
|
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Return the address of a given value
|
71
|
+
#
|
72
|
+
# @yield [Object] yields each cell value to the block
|
73
|
+
# @return [String, nil] the address of the value or nil
|
74
|
+
#
|
35
75
|
|
36
76
|
def find
|
77
|
+
return to_enum( :find ) unless block_given?
|
37
78
|
each_cell { |ce| return ce.address if yield ce.value }; nil
|
38
79
|
end
|
39
80
|
|
81
|
+
#
|
82
|
+
# View the object for debugging
|
83
|
+
#
|
84
|
+
|
40
85
|
def inspect
|
41
86
|
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ idx }"
|
42
87
|
end
|
43
88
|
|
89
|
+
#
|
90
|
+
# Read a value by address
|
91
|
+
#
|
92
|
+
# @param [String, Fixnum] id the index or reference of the required value
|
93
|
+
#
|
94
|
+
|
44
95
|
def read( id )
|
45
96
|
data[ translate_address( id ) ]
|
46
97
|
end
|
47
98
|
alias [] read
|
48
99
|
|
100
|
+
#
|
101
|
+
# Summarise the values of a Section into a Hash
|
102
|
+
#
|
103
|
+
# @return [Hash]
|
104
|
+
#
|
105
|
+
|
49
106
|
def summarise
|
50
107
|
each_wh.inject( Hash.new(0) ) { |h, v| h[v]+=1; h }
|
51
108
|
end
|
52
109
|
alias summarize summarise
|
53
110
|
|
111
|
+
#
|
112
|
+
# The Section as a seperated value String
|
113
|
+
#
|
114
|
+
|
54
115
|
def to_s
|
55
116
|
to_a.join ( self.is_a?( Row ) ? "\t" : "\n" )
|
56
117
|
end
|
57
118
|
|
119
|
+
#
|
120
|
+
# Write a value by address
|
121
|
+
#
|
122
|
+
# @param [String, Fixnum] id the index or reference to write to
|
123
|
+
# @param [Object] val the object to place at the address
|
124
|
+
#
|
125
|
+
|
58
126
|
def write( id, val )
|
59
127
|
data[ translate_address( id ) ] = val
|
60
128
|
end
|
61
129
|
alias []= write
|
62
130
|
|
63
|
-
|
131
|
+
#
|
132
|
+
# Yields each value
|
133
|
+
#
|
64
134
|
|
65
135
|
def each
|
66
136
|
return to_enum(:each) unless block_given?
|
67
137
|
each_address { |addr| yield data[ addr ] }
|
68
138
|
end
|
69
139
|
|
140
|
+
#
|
141
|
+
# Yields each value, skipping headers
|
142
|
+
#
|
143
|
+
|
70
144
|
def each_without_headers
|
71
|
-
return to_enum(:each_without_headers) unless block_given?
|
145
|
+
return to_enum( :each_without_headers ) unless block_given?
|
72
146
|
each_address_without_headers { |addr| yield data[ addr ] }
|
73
147
|
end
|
74
148
|
alias each_wh each_without_headers
|
75
149
|
|
150
|
+
#
|
151
|
+
# Yields each cell
|
152
|
+
#
|
153
|
+
|
76
154
|
def each_cell
|
77
|
-
return to_enum(:each_cell) unless block_given?
|
155
|
+
return to_enum( :each_cell ) unless block_given?
|
78
156
|
each_address { |addr| yield Element.new( sheet, addr ) }
|
79
157
|
end
|
80
158
|
|
159
|
+
#
|
160
|
+
# Yields each cell, skipping headers
|
161
|
+
#
|
162
|
+
|
81
163
|
def each_cell_without_headers
|
82
|
-
return to_enum(:
|
164
|
+
return to_enum( :each_cell_without_headers ) unless block_given?
|
83
165
|
each_address { |addr| yield Element.new( sheet, addr ) }
|
84
166
|
end
|
85
167
|
alias each_cell_wh each_cell_without_headers
|
86
168
|
|
169
|
+
#
|
170
|
+
# Replaces each value with the result of the block
|
171
|
+
#
|
172
|
+
|
87
173
|
def map!
|
88
|
-
return to_enum(:map!) unless block_given?
|
174
|
+
return to_enum( :map! ) unless block_given?
|
89
175
|
each_address { |addr| data[addr] = ( yield data[addr] ) }
|
90
176
|
end
|
91
177
|
|
@@ -93,7 +179,8 @@ module RubyExcel
|
|
93
179
|
|
94
180
|
def translate_address( addr )
|
95
181
|
case self
|
96
|
-
when Row
|
182
|
+
when Row
|
183
|
+
col_letter( addr ) + idx.to_s
|
97
184
|
when Column
|
98
185
|
addr = addr.to_s unless addr.is_a?( String )
|
99
186
|
fail ArgumentError, "Invalid address : #{ addr }" if addr =~ /[^\d]/
|
@@ -103,24 +190,61 @@ module RubyExcel
|
|
103
190
|
|
104
191
|
end
|
105
192
|
|
193
|
+
#
|
194
|
+
# A Row in the Sheet
|
195
|
+
#
|
196
|
+
|
106
197
|
class Row < Section
|
107
198
|
|
199
|
+
# The Row index
|
108
200
|
attr_reader :idx
|
201
|
+
|
202
|
+
#
|
203
|
+
# Creates a RubyExcel::Row instance
|
204
|
+
#
|
205
|
+
# @param [RubyExcel::Sheet] sheet the Sheet which holds this Row
|
206
|
+
# @param [Fixnum] idx the index of this Row
|
207
|
+
#
|
109
208
|
|
110
209
|
def initialize( sheet, idx )
|
111
210
|
@idx = idx.to_i
|
112
211
|
super( sheet )
|
113
212
|
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# Access a cell by its header
|
216
|
+
#
|
217
|
+
# @param [String] header the header to search for
|
218
|
+
# @return [RubyExcel::Element] the cell
|
219
|
+
#
|
114
220
|
|
115
221
|
def cell_by_header( header )
|
116
222
|
cell( getref( header ) )
|
117
223
|
end
|
118
224
|
alias cell_h cell_by_header
|
119
225
|
|
226
|
+
#
|
227
|
+
# Find the Address of a header
|
228
|
+
#
|
229
|
+
# @param [String] header the header to search for
|
230
|
+
# @return [String] the address of the header
|
231
|
+
#
|
232
|
+
|
120
233
|
def getref( header )
|
121
|
-
|
234
|
+
sheet.header_rows.times do |t|
|
235
|
+
res = sheet.row( t + 1 ).find &/^#{header}$/
|
236
|
+
return column_id( res ) if res
|
237
|
+
end
|
238
|
+
fail ArgumentError, 'Invalid header: ' + header.to_s
|
122
239
|
end
|
123
240
|
|
241
|
+
#
|
242
|
+
# Find a value in this Row by its header
|
243
|
+
#
|
244
|
+
# @param [String]header the header to search for
|
245
|
+
# @return [Object] the value at the address
|
246
|
+
#
|
247
|
+
|
124
248
|
def value_by_header( header )
|
125
249
|
self[ getref( header ) ]
|
126
250
|
end
|
@@ -133,15 +257,27 @@ module RubyExcel
|
|
133
257
|
end
|
134
258
|
|
135
259
|
def each_address_without_headers
|
136
|
-
(
|
260
|
+
( 'A'..col_letter( data.cols ) ).each { |col_id| yield "#{col_id}#{idx}" }
|
137
261
|
end
|
138
262
|
|
139
263
|
end
|
140
264
|
|
265
|
+
#
|
266
|
+
# A Column in the Sheet
|
267
|
+
#
|
268
|
+
|
141
269
|
class Column < Section
|
142
270
|
|
271
|
+
# The Row index
|
143
272
|
attr_reader :idx
|
144
273
|
|
274
|
+
#
|
275
|
+
# Creates a RubyExcel::Column instance
|
276
|
+
#
|
277
|
+
# @param [RubyExcel::Sheet] sheet the Sheet which holds this Column
|
278
|
+
# @param [String, Fixnum] idx the index of this Column
|
279
|
+
#
|
280
|
+
|
145
281
|
def initialize( sheet, idx )
|
146
282
|
@idx = idx
|
147
283
|
super( sheet )
|