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 +4 -4
- data/LICENSE.md +9 -9
- data/README.md +782 -779
- data/lib/rubyexcel.rb +264 -260
- data/lib/rubyexcel/address.rb +187 -187
- data/lib/rubyexcel/data.rb +450 -450
- data/lib/rubyexcel/element.rb +226 -226
- data/lib/rubyexcel/excel_tools.rb +287 -261
- data/lib/rubyexcel/rubyexcel_components.rb +70 -70
- data/lib/rubyexcel/section.rb +376 -376
- data/lib/rubyexcel/sheet.rb +720 -720
- metadata +6 -7
data/lib/rubyexcel/element.rb
CHANGED
@@ -1,226 +1,226 @@
|
|
1
|
-
module RubyExcel
|
2
|
-
|
3
|
-
#
|
4
|
-
# A Range or Cell in a Sheet
|
5
|
-
#
|
6
|
-
|
7
|
-
class Element
|
8
|
-
include Address
|
9
|
-
include Enumerable
|
10
|
-
|
11
|
-
#The parent Sheet
|
12
|
-
attr_reader :sheet
|
13
|
-
alias parent sheet
|
14
|
-
|
15
|
-
#The address
|
16
|
-
attr_reader :address
|
17
|
-
|
18
|
-
#The Data underlying the Sheet
|
19
|
-
attr_reader :data
|
20
|
-
|
21
|
-
#The first Column id in the address
|
22
|
-
attr_reader :column
|
23
|
-
|
24
|
-
#The first Row id in the address
|
25
|
-
attr_reader :row
|
26
|
-
|
27
|
-
#
|
28
|
-
# Creates a RubyExcel::Element instance
|
29
|
-
#
|
30
|
-
# @param [RubyExcel::Sheet] sheet the parent Sheet
|
31
|
-
# @param [String] addr the address to reference
|
32
|
-
#
|
33
|
-
|
34
|
-
def initialize( sheet, addr )
|
35
|
-
@sheet = sheet
|
36
|
-
@data = sheet.data
|
37
|
-
@address = addr
|
38
|
-
@column = ( addr =~ /[a-z]/i ? column_id( addr ) : 'A' )
|
39
|
-
@row = ( addr =~ /\d/ ? row_id( addr ) : 1 )
|
40
|
-
end
|
41
|
-
|
42
|
-
#
|
43
|
-
# Delete the data referenced by self.address
|
44
|
-
#
|
45
|
-
|
46
|
-
def delete
|
47
|
-
data.delete( self ); self
|
48
|
-
end
|
49
|
-
|
50
|
-
#
|
51
|
-
# Yields each value in the data referenced by the address
|
52
|
-
#
|
53
|
-
|
54
|
-
def each
|
55
|
-
return to_enum( :each ) unless block_given?
|
56
|
-
expand( address ).flatten.each { |addr| yield data[ addr ] }
|
57
|
-
end
|
58
|
-
|
59
|
-
#
|
60
|
-
# Yields each Element referenced by the address
|
61
|
-
#
|
62
|
-
|
63
|
-
def each_cell
|
64
|
-
return to_enum( :each_cell ) unless block_given?
|
65
|
-
expand( address ).flatten.each { |addr| yield Cell.new( sheet, addr ) }
|
66
|
-
end
|
67
|
-
|
68
|
-
#
|
69
|
-
# Checks whether the data referenced by the address is empty
|
70
|
-
#
|
71
|
-
|
72
|
-
def empty?
|
73
|
-
all? { |v| v.to_s.empty? }
|
74
|
-
end
|
75
|
-
|
76
|
-
#
|
77
|
-
# Return the first cell in the Range
|
78
|
-
#
|
79
|
-
# @return [RubyExcel::Cell]
|
80
|
-
#
|
81
|
-
|
82
|
-
def first_cell
|
83
|
-
Cell.new( sheet, expand( address ).flatten.first )
|
84
|
-
end
|
85
|
-
|
86
|
-
#
|
87
|
-
# View the object for debugging
|
88
|
-
#
|
89
|
-
|
90
|
-
def inspect
|
91
|
-
"#{ self.class }:0x#{ '%x' % ( object_id << 1 ) }: '#{ address }'"
|
92
|
-
end
|
93
|
-
|
94
|
-
#
|
95
|
-
# Return the last cell in the Range
|
96
|
-
#
|
97
|
-
# @return [RubyExcel::Cell]
|
98
|
-
#
|
99
|
-
|
100
|
-
def last_cell
|
101
|
-
Cell.new( sheet, expand( address ).flatten.last )
|
102
|
-
end
|
103
|
-
|
104
|
-
#
|
105
|
-
# Replaces each value with the result of the block
|
106
|
-
#
|
107
|
-
|
108
|
-
def map!
|
109
|
-
return to_enum( :map! ) unless block_given?
|
110
|
-
expand( address ).flatten.each { |addr| data[ addr ] = yield data[ addr ] }
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
#
|
116
|
-
# A single Cell
|
117
|
-
#
|
118
|
-
|
119
|
-
class Cell < Element
|
120
|
-
|
121
|
-
def initialize( sheet, addr )
|
122
|
-
fail ArgumentError, "Invalid Cell address: #{ addr }" unless addr =~ /\A[A-Z]{1,3}\d+\z/i
|
123
|
-
super
|
124
|
-
end
|
125
|
-
|
126
|
-
#
|
127
|
-
# Return the value at this Cell's address
|
128
|
-
#
|
129
|
-
# @return [Object ] the Object within the data, referenced by the address
|
130
|
-
#
|
131
|
-
|
132
|
-
def value
|
133
|
-
data[ address ]
|
134
|
-
end
|
135
|
-
|
136
|
-
#
|
137
|
-
# Set the value at this Cell's address
|
138
|
-
#
|
139
|
-
# @param [Object] val the Object to write into the data
|
140
|
-
#
|
141
|
-
|
142
|
-
def value=( val )
|
143
|
-
data[ address ] = val
|
144
|
-
end
|
145
|
-
|
146
|
-
#
|
147
|
-
# The data at address as a String
|
148
|
-
#
|
149
|
-
|
150
|
-
def to_s
|
151
|
-
val.to_s
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
#
|
157
|
-
# A Range of Cells
|
158
|
-
#
|
159
|
-
|
160
|
-
class Range < Element
|
161
|
-
|
162
|
-
def initialize( sheet, addr )
|
163
|
-
fail ArgumentError, "Invalid Range address: #{ addr }" unless addr =~ /\A[A-Z]{1,3}\d+:[A-Z]{1,3}\d+\z|\A[A-Z]{1,3}:[A-Z]{1,3}\z|\A\d+:\d+\z/i
|
164
|
-
super
|
165
|
-
end
|
166
|
-
|
167
|
-
#
|
168
|
-
# Return the value at this Range's address
|
169
|
-
#
|
170
|
-
# @return [Array<Object>] the Array of Objects within the data, referenced by the address
|
171
|
-
#
|
172
|
-
|
173
|
-
def value
|
174
|
-
expand( address ).map { |ar| ar.map { |addr| data[ addr ] } }
|
175
|
-
end
|
176
|
-
|
177
|
-
#
|
178
|
-
# Set the value at this Range's address
|
179
|
-
#
|
180
|
-
# @param [Object, Array<Object>] val the Object or Array of Objects to write into the data
|
181
|
-
#
|
182
|
-
|
183
|
-
def value=( val )
|
184
|
-
|
185
|
-
addresses = expand( address )
|
186
|
-
|
187
|
-
# 2D Array of Values
|
188
|
-
if multi_array?( val ) && addresses.length > 1
|
189
|
-
|
190
|
-
# Check the dimensions
|
191
|
-
val_rows, val_cols, range_rows, range_cols = val.length, val.max_by(&:length).length, addresses.length, addresses.max_by(&:length).length
|
192
|
-
val_rows == range_rows && val_cols == range_cols or fail ArgumentError, "Dimension mismatch! Value - rows: #{val_rows}, columns: #{ val_cols }. Range - rows: #{ range_rows }, columns: #{ range_cols }"
|
193
|
-
|
194
|
-
# Write the values in order
|
195
|
-
addresses.each_with_index { |row,idx| row.each_with_index { |el,i| data[el] = val[idx][i] } }
|
196
|
-
|
197
|
-
# Array of Values
|
198
|
-
elsif val.is_a?( Array )
|
199
|
-
|
200
|
-
# Write the values in order
|
201
|
-
addresses.flatten.each_with_index { |addr, i| data[addr] = val[i] }
|
202
|
-
|
203
|
-
# Single Value
|
204
|
-
else
|
205
|
-
|
206
|
-
# Write the same value to every cell in the Range
|
207
|
-
addresses.each { |ar| ar.each { |addr| data[ addr ] = val } }
|
208
|
-
|
209
|
-
end
|
210
|
-
|
211
|
-
val
|
212
|
-
end
|
213
|
-
|
214
|
-
#
|
215
|
-
# The data at address as a TSV String
|
216
|
-
#
|
217
|
-
|
218
|
-
def to_s
|
219
|
-
value.map { |ar| ar.map { |v| v.to_s.gsub(/\t|\n|\r/,' ') }.join "\t" }.join($/)
|
220
|
-
end
|
221
|
-
|
222
|
-
|
223
|
-
end
|
224
|
-
|
225
|
-
end
|
226
|
-
|
1
|
+
module RubyExcel
|
2
|
+
|
3
|
+
#
|
4
|
+
# A Range or Cell in a Sheet
|
5
|
+
#
|
6
|
+
|
7
|
+
class Element
|
8
|
+
include Address
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
#The parent Sheet
|
12
|
+
attr_reader :sheet
|
13
|
+
alias parent sheet
|
14
|
+
|
15
|
+
#The address
|
16
|
+
attr_reader :address
|
17
|
+
|
18
|
+
#The Data underlying the Sheet
|
19
|
+
attr_reader :data
|
20
|
+
|
21
|
+
#The first Column id in the address
|
22
|
+
attr_reader :column
|
23
|
+
|
24
|
+
#The first Row id in the address
|
25
|
+
attr_reader :row
|
26
|
+
|
27
|
+
#
|
28
|
+
# Creates a RubyExcel::Element instance
|
29
|
+
#
|
30
|
+
# @param [RubyExcel::Sheet] sheet the parent Sheet
|
31
|
+
# @param [String] addr the address to reference
|
32
|
+
#
|
33
|
+
|
34
|
+
def initialize( sheet, addr )
|
35
|
+
@sheet = sheet
|
36
|
+
@data = sheet.data
|
37
|
+
@address = addr
|
38
|
+
@column = ( addr =~ /[a-z]/i ? column_id( addr ) : 'A' )
|
39
|
+
@row = ( addr =~ /\d/ ? row_id( addr ) : 1 )
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Delete the data referenced by self.address
|
44
|
+
#
|
45
|
+
|
46
|
+
def delete
|
47
|
+
data.delete( self ); self
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Yields each value in the data referenced by the address
|
52
|
+
#
|
53
|
+
|
54
|
+
def each
|
55
|
+
return to_enum( :each ) unless block_given?
|
56
|
+
expand( address ).flatten.each { |addr| yield data[ addr ] }
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Yields each Element referenced by the address
|
61
|
+
#
|
62
|
+
|
63
|
+
def each_cell
|
64
|
+
return to_enum( :each_cell ) unless block_given?
|
65
|
+
expand( address ).flatten.each { |addr| yield Cell.new( sheet, addr ) }
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Checks whether the data referenced by the address is empty
|
70
|
+
#
|
71
|
+
|
72
|
+
def empty?
|
73
|
+
all? { |v| v.to_s.empty? }
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Return the first cell in the Range
|
78
|
+
#
|
79
|
+
# @return [RubyExcel::Cell]
|
80
|
+
#
|
81
|
+
|
82
|
+
def first_cell
|
83
|
+
Cell.new( sheet, expand( address ).flatten.first )
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# View the object for debugging
|
88
|
+
#
|
89
|
+
|
90
|
+
def inspect
|
91
|
+
"#{ self.class }:0x#{ '%x' % ( object_id << 1 ) }: '#{ address }'"
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Return the last cell in the Range
|
96
|
+
#
|
97
|
+
# @return [RubyExcel::Cell]
|
98
|
+
#
|
99
|
+
|
100
|
+
def last_cell
|
101
|
+
Cell.new( sheet, expand( address ).flatten.last )
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Replaces each value with the result of the block
|
106
|
+
#
|
107
|
+
|
108
|
+
def map!
|
109
|
+
return to_enum( :map! ) unless block_given?
|
110
|
+
expand( address ).flatten.each { |addr| data[ addr ] = yield data[ addr ] }
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# A single Cell
|
117
|
+
#
|
118
|
+
|
119
|
+
class Cell < Element
|
120
|
+
|
121
|
+
def initialize( sheet, addr )
|
122
|
+
fail ArgumentError, "Invalid Cell address: #{ addr }" unless addr =~ /\A[A-Z]{1,3}\d+\z/i
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Return the value at this Cell's address
|
128
|
+
#
|
129
|
+
# @return [Object ] the Object within the data, referenced by the address
|
130
|
+
#
|
131
|
+
|
132
|
+
def value
|
133
|
+
data[ address ]
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Set the value at this Cell's address
|
138
|
+
#
|
139
|
+
# @param [Object] val the Object to write into the data
|
140
|
+
#
|
141
|
+
|
142
|
+
def value=( val )
|
143
|
+
data[ address ] = val
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# The data at address as a String
|
148
|
+
#
|
149
|
+
|
150
|
+
def to_s
|
151
|
+
val.to_s
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# A Range of Cells
|
158
|
+
#
|
159
|
+
|
160
|
+
class Range < Element
|
161
|
+
|
162
|
+
def initialize( sheet, addr )
|
163
|
+
fail ArgumentError, "Invalid Range address: #{ addr }" unless addr =~ /\A[A-Z]{1,3}\d+:[A-Z]{1,3}\d+\z|\A[A-Z]{1,3}:[A-Z]{1,3}\z|\A\d+:\d+\z/i
|
164
|
+
super
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Return the value at this Range's address
|
169
|
+
#
|
170
|
+
# @return [Array<Object>] the Array of Objects within the data, referenced by the address
|
171
|
+
#
|
172
|
+
|
173
|
+
def value
|
174
|
+
expand( address ).map { |ar| ar.map { |addr| data[ addr ] } }
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
# Set the value at this Range's address
|
179
|
+
#
|
180
|
+
# @param [Object, Array<Object>] val the Object or Array of Objects to write into the data
|
181
|
+
#
|
182
|
+
|
183
|
+
def value=( val )
|
184
|
+
|
185
|
+
addresses = expand( address )
|
186
|
+
|
187
|
+
# 2D Array of Values
|
188
|
+
if multi_array?( val ) && addresses.length > 1
|
189
|
+
|
190
|
+
# Check the dimensions
|
191
|
+
val_rows, val_cols, range_rows, range_cols = val.length, val.max_by(&:length).length, addresses.length, addresses.max_by(&:length).length
|
192
|
+
val_rows == range_rows && val_cols == range_cols or fail ArgumentError, "Dimension mismatch! Value - rows: #{val_rows}, columns: #{ val_cols }. Range - rows: #{ range_rows }, columns: #{ range_cols }"
|
193
|
+
|
194
|
+
# Write the values in order
|
195
|
+
addresses.each_with_index { |row,idx| row.each_with_index { |el,i| data[el] = val[idx][i] } }
|
196
|
+
|
197
|
+
# Array of Values
|
198
|
+
elsif val.is_a?( Array )
|
199
|
+
|
200
|
+
# Write the values in order
|
201
|
+
addresses.flatten.each_with_index { |addr, i| data[addr] = val[i] }
|
202
|
+
|
203
|
+
# Single Value
|
204
|
+
else
|
205
|
+
|
206
|
+
# Write the same value to every cell in the Range
|
207
|
+
addresses.each { |ar| ar.each { |addr| data[ addr ] = val } }
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
val
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# The data at address as a TSV String
|
216
|
+
#
|
217
|
+
|
218
|
+
def to_s
|
219
|
+
value.map { |ar| ar.map { |v| v.to_s.gsub(/\t|\n|\r/,' ') }.join "\t" }.join($/)
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
@@ -1,262 +1,288 @@
|
|
1
|
-
require 'win32ole' #Interface with Excel
|
2
|
-
require 'win32/registry' #Find Documents / My Documents for default directory
|
3
|
-
|
4
|
-
# Holder for WIN32OLE Excel Constants
|
5
|
-
module ExcelConstants; end
|
6
|
-
|
7
|
-
module RubyExcel
|
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
|
-
|
19
|
-
def self.borders( range, weight=1, inner=false )
|
20
|
-
range.ole_respond_to?( :borders ) or fail ArgumentError, 'First Argument must be WIN32OLE Range'
|
21
|
-
[0,1,2,3].include?( weight ) or fail ArgumentError, "Invalid line weight #{ weight }. Must be from 0 to 3"
|
22
|
-
defined?( ExcelConstants::XlEdgeLeft ) or WIN32OLE.const_load( range.application, ExcelConstants )
|
23
|
-
consts = [ ExcelConstants::XlEdgeLeft, ExcelConstants::XlEdgeTop, ExcelConstants::XlEdgeBottom, ExcelConstants::XlEdgeRight, ExcelConstants::XlInsideVertical, ExcelConstants::XlInsideHorizontal ]
|
24
|
-
inner or consts.pop(2)
|
25
|
-
weight = [ 0, ExcelConstants::XlThin, ExcelConstants::XlMedium, ExcelConstants::XlThick ][ weight ]
|
26
|
-
consts.each { |const| weight.zero? ? range.Borders( const ).linestyle = ExcelConstants::XlNone : range.Borders( const ).weight = weight }
|
27
|
-
range
|
28
|
-
end
|
29
|
-
|
30
|
-
#
|
31
|
-
# Find the Windows "Documents" or "My Documents" path, or return the present working directory if it can't be found.
|
32
|
-
#
|
33
|
-
# @return [String]
|
34
|
-
#
|
35
|
-
|
36
|
-
def self.documents_path
|
37
|
-
Win32::Registry::HKEY_CURRENT_USER.open( 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders' )['Personal'] rescue Dir.pwd.gsub('/','\\')
|
38
|
-
end
|
39
|
-
|
40
|
-
class Workbook
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
# Add a single quote before any equals sign in the data.
|
45
|
-
# Disables any Strings which would have been interpreted as formulas by Excel
|
46
|
-
#
|
47
|
-
|
48
|
-
def disable_formulas!
|
49
|
-
sheets { |s| s.rows { |r| r.each_cell { |ce|
|
50
|
-
if ce.value.is_a?( String ) && ce.value[0] == '='
|
51
|
-
ce.value = ce.value.sub( /\A=/,"'=" )
|
52
|
-
end
|
53
|
-
} } }; self
|
54
|
-
end
|
55
|
-
|
56
|
-
#
|
57
|
-
# Find the Windows "Documents" or "My Documents" path, or return the present working directory if it can't be found.
|
58
|
-
#
|
59
|
-
# @return [String]
|
60
|
-
#
|
61
|
-
|
62
|
-
def documents_path
|
63
|
-
RubyExcel.documents_path
|
64
|
-
end
|
65
|
-
|
66
|
-
#
|
67
|
-
# Drop a multidimensional Array into an Excel Sheet
|
68
|
-
#
|
69
|
-
# @param [Array<Array>] data the data to place in the Sheet
|
70
|
-
# @param [WIN32OLE::Worksheet, nil] sheet optional WIN32OLE Worksheet to use
|
71
|
-
# @return [WIN32OLE::Worksheet] the Worksheet containing the data
|
72
|
-
#
|
73
|
-
|
74
|
-
def dump_to_sheet( data, sheet=nil )
|
75
|
-
data.is_a?( Array ) or fail ArgumentError, "Invalid data type: #{ data.class }"
|
76
|
-
sheet ||= get_workbook.sheets(1)
|
77
|
-
sheet.cells.clear
|
78
|
-
sheet.range( sheet.cells( 1, 1 ), sheet.cells( data.length, data.max_by(&:length).length ) ).value = data
|
79
|
-
sheet
|
80
|
-
end
|
81
|
-
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
# @param [
|
86
|
-
# @return [
|
87
|
-
#
|
88
|
-
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
|
255
|
-
def
|
256
|
-
sheets &:
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
1
|
+
require 'win32ole' #Interface with Excel
|
2
|
+
require 'win32/registry' #Find Documents / My Documents for default directory
|
3
|
+
|
4
|
+
# Holder for WIN32OLE Excel Constants
|
5
|
+
module ExcelConstants; end
|
6
|
+
|
7
|
+
module RubyExcel
|
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
|
+
|
19
|
+
def self.borders( range, weight=1, inner=false )
|
20
|
+
range.ole_respond_to?( :borders ) or fail ArgumentError, 'First Argument must be WIN32OLE Range'
|
21
|
+
[0,1,2,3].include?( weight ) or fail ArgumentError, "Invalid line weight #{ weight }. Must be from 0 to 3"
|
22
|
+
defined?( ExcelConstants::XlEdgeLeft ) or WIN32OLE.const_load( range.application, ExcelConstants )
|
23
|
+
consts = [ ExcelConstants::XlEdgeLeft, ExcelConstants::XlEdgeTop, ExcelConstants::XlEdgeBottom, ExcelConstants::XlEdgeRight, ExcelConstants::XlInsideVertical, ExcelConstants::XlInsideHorizontal ]
|
24
|
+
inner or consts.pop(2)
|
25
|
+
weight = [ 0, ExcelConstants::XlThin, ExcelConstants::XlMedium, ExcelConstants::XlThick ][ weight ]
|
26
|
+
consts.each { |const| weight.zero? ? range.Borders( const ).linestyle = ExcelConstants::XlNone : range.Borders( const ).weight = weight }
|
27
|
+
range
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Find the Windows "Documents" or "My Documents" path, or return the present working directory if it can't be found.
|
32
|
+
#
|
33
|
+
# @return [String]
|
34
|
+
#
|
35
|
+
|
36
|
+
def self.documents_path
|
37
|
+
Win32::Registry::HKEY_CURRENT_USER.open( 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders' )['Personal'] rescue Dir.pwd.gsub('/','\\')
|
38
|
+
end
|
39
|
+
|
40
|
+
class Workbook
|
41
|
+
|
42
|
+
|
43
|
+
#
|
44
|
+
# Add a single quote before any equals sign in the data.
|
45
|
+
# Disables any Strings which would have been interpreted as formulas by Excel
|
46
|
+
#
|
47
|
+
|
48
|
+
def disable_formulas!
|
49
|
+
sheets { |s| s.rows { |r| r.each_cell { |ce|
|
50
|
+
if ce.value.is_a?( String ) && ce.value[0] == '='
|
51
|
+
ce.value = ce.value.sub( /\A=/,"'=" )
|
52
|
+
end
|
53
|
+
} } }; self
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Find the Windows "Documents" or "My Documents" path, or return the present working directory if it can't be found.
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
#
|
61
|
+
|
62
|
+
def documents_path
|
63
|
+
RubyExcel.documents_path
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Drop a multidimensional Array into an Excel Sheet
|
68
|
+
#
|
69
|
+
# @param [Array<Array>] data the data to place in the Sheet
|
70
|
+
# @param [WIN32OLE::Worksheet, nil] sheet optional WIN32OLE Worksheet to use
|
71
|
+
# @return [WIN32OLE::Worksheet] the Worksheet containing the data
|
72
|
+
#
|
73
|
+
|
74
|
+
def dump_to_sheet( data, sheet=nil )
|
75
|
+
data.is_a?( Array ) or fail ArgumentError, "Invalid data type: #{ data.class }"
|
76
|
+
sheet ||= get_workbook.sheets(1)
|
77
|
+
sheet.cells.clear
|
78
|
+
sheet.range( sheet.cells( 1, 1 ), sheet.cells( data.length, data.max_by(&:length).length ) ).value = data
|
79
|
+
sheet
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Save the RubyExcel::Workbook as an Excel Workbook and close Excel afterwards
|
84
|
+
#
|
85
|
+
# @param [String] filename the filename to save as
|
86
|
+
# @return [String] the full filename
|
87
|
+
#
|
88
|
+
|
89
|
+
def export( filename = nil )
|
90
|
+
prev_standalone = standalone
|
91
|
+
self.standalone = true
|
92
|
+
filename ||= name
|
93
|
+
filename = filename.gsub('/','\\')
|
94
|
+
unless filename.include?('\\')
|
95
|
+
filename = documents_path + '\\' + filename
|
96
|
+
end
|
97
|
+
wb = to_excel( true )
|
98
|
+
wb.saveas filename
|
99
|
+
filename = wb.fullname
|
100
|
+
excel = wb.application
|
101
|
+
wb.close
|
102
|
+
excel.quit
|
103
|
+
self.standalone = prev_standalone
|
104
|
+
filename
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Open or connect to an Excel instance
|
109
|
+
#
|
110
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
111
|
+
# @return [WIN32OLE::Excel] the first available Excel application
|
112
|
+
#
|
113
|
+
|
114
|
+
def get_excel( invisible = false )
|
115
|
+
return WIN32OLE::new( 'excel.application' ) if standalone
|
116
|
+
excel = WIN32OLE::connect( 'excel.application' ) rescue WIN32OLE::new( 'excel.application' )
|
117
|
+
excel.visible = true unless invisible
|
118
|
+
excel
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Create a new Excel Workbook
|
123
|
+
#
|
124
|
+
# @param [WIN32OLE::Excel, nil] excel an Excel object to use
|
125
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
126
|
+
# @return [WIN32OLE::Workbook] the new Excel Workbook
|
127
|
+
#
|
128
|
+
|
129
|
+
def get_workbook( excel=nil, invisible = false )
|
130
|
+
excel ||= get_excel( invisible )
|
131
|
+
wb = excel.workbooks.add
|
132
|
+
( ( wb.sheets.count.to_i ) - 1 ).times { |time| wb.sheets(2).delete }
|
133
|
+
wb
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Import a WIN32OLE Object as a Workbook or Sheet
|
138
|
+
#
|
139
|
+
# @param [WIN32OLE::Workbook, WIN32OLE::Sheet, String] other The WIN32OLE Object, either Sheet or Workbook, to import, or a path to the file.
|
140
|
+
# @param [String] sheetname the name of a specific Sheet to import.
|
141
|
+
# @param [Boolean] keep_formulas Retain Excel formulas rather than importing their current values
|
142
|
+
# @return [self] self with the data and name(s) imported.
|
143
|
+
#
|
144
|
+
|
145
|
+
def import( other, sheetname=nil, keep_formulas=false )
|
146
|
+
operation = ( keep_formulas ? :formula : :value )
|
147
|
+
|
148
|
+
if other.is_a?( String )
|
149
|
+
|
150
|
+
# Filename
|
151
|
+
File.exists?( other ) || fail( ArgumentError, "Unable to find file: #{ other }" )
|
152
|
+
|
153
|
+
#Open Excel
|
154
|
+
excel = WIN32OLE.new( 'excel.application' )
|
155
|
+
excel.displayalerts = false
|
156
|
+
|
157
|
+
# Open the file
|
158
|
+
begin
|
159
|
+
wb = excel.workbooks.open({'filename'=> other, 'readOnly' => true, 'UpdateLinks' => false})
|
160
|
+
rescue WIN32OLERuntimeError
|
161
|
+
excel.quit
|
162
|
+
raise
|
163
|
+
end
|
164
|
+
|
165
|
+
# Only one sheet, or the entire Workbook?
|
166
|
+
if sheetname
|
167
|
+
|
168
|
+
add( sheetname ).load( wb.sheets( sheetname ).usedrange.send( operation ) )
|
169
|
+
|
170
|
+
else
|
171
|
+
|
172
|
+
self.name = File.basename( other, '.*' )
|
173
|
+
wb.sheets.each { |sh| add( sh.name ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? } }
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
# Cleanup
|
178
|
+
wb.close
|
179
|
+
excel.quit
|
180
|
+
|
181
|
+
elsif !other.respond_to?( :ole_respond_to? )
|
182
|
+
|
183
|
+
fail ArgumentError, "Invalid input: #{other.class}"
|
184
|
+
|
185
|
+
elsif other.ole_respond_to?( :sheets )
|
186
|
+
|
187
|
+
# Workbook
|
188
|
+
|
189
|
+
# Only one sheet, or the entire Workbook?
|
190
|
+
if sheetname
|
191
|
+
add( sheetname ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? }
|
192
|
+
else
|
193
|
+
self.name = File.basename( other.name, '.*' )
|
194
|
+
other.sheets.each { |sh| add( sh.name ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? } }
|
195
|
+
end
|
196
|
+
|
197
|
+
elsif other.ole_respond_to?( :usedrange )
|
198
|
+
|
199
|
+
# Sheet
|
200
|
+
add( other.name ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? }
|
201
|
+
|
202
|
+
else
|
203
|
+
|
204
|
+
fail ArgumentError, "Object not recognised as a WIN32OLE Workbook or Sheet.\n#{other.inspect}"
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# Take an Excel Sheet and standardise some of the formatting
|
213
|
+
#
|
214
|
+
# @param [WIN32OLE::Worksheet] sheet the Sheet to add formatting to
|
215
|
+
# @return [WIN32OLE::Worksheet] the sheet with formatting added
|
216
|
+
#
|
217
|
+
|
218
|
+
def make_sheet_pretty( sheet )
|
219
|
+
c = sheet.cells
|
220
|
+
c.rowheight = 15
|
221
|
+
c.entireColumn.autoFit
|
222
|
+
c.horizontalAlignment = -4108
|
223
|
+
c.verticalAlignment = -4108
|
224
|
+
sheet.UsedRange.Columns.each { |col| col.ColumnWidth = 30 if col.ColumnWidth > 50 }
|
225
|
+
RubyExcel.borders( sheet.usedrange, 1, true )
|
226
|
+
sheet
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# Save the RubyExcel::Workbook as an Excel Workbook
|
231
|
+
#
|
232
|
+
# @param [String] filename the filename to save as
|
233
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
234
|
+
# @return [WIN32OLE::Workbook] the Workbook, saved as filename.
|
235
|
+
#
|
236
|
+
|
237
|
+
def save_excel( filename = nil, invisible = false )
|
238
|
+
filename ||= name
|
239
|
+
filename = filename.gsub('/','\\')
|
240
|
+
unless filename.include?('\\')
|
241
|
+
filename = documents_path + '\\' + filename
|
242
|
+
end
|
243
|
+
wb = to_excel( ( standalone ? true : invisible ) )
|
244
|
+
wb.saveas filename
|
245
|
+
wb
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# Output the RubyExcel::Workbook to Excel
|
250
|
+
#
|
251
|
+
# @param [Boolean] invisible leave Excel invisible if creating a new instance
|
252
|
+
# @return [WIN32OLE::Workbook] the Workbook in Excel
|
253
|
+
#
|
254
|
+
|
255
|
+
def to_excel( invisible = false )
|
256
|
+
self.sheets.count == sheets.map(&:name).uniq.length or fail NoMethodError, 'Duplicate sheet name'
|
257
|
+
wb = get_workbook( nil, true )
|
258
|
+
wb.parent.displayAlerts = false
|
259
|
+
first_time = true
|
260
|
+
each do |s|
|
261
|
+
sht = ( first_time ? wb.sheets(1) : wb.sheets.add( { 'after' => wb.sheets( wb.sheets.count ) } ) ); first_time = false
|
262
|
+
sht.name = s.name
|
263
|
+
make_sheet_pretty( dump_to_sheet( s.to_a, sht ) )
|
264
|
+
end
|
265
|
+
wb.sheets(1).select rescue nil
|
266
|
+
wb.application.visible = true unless invisible
|
267
|
+
wb
|
268
|
+
end
|
269
|
+
|
270
|
+
# {Workbook#to_safe_format!}
|
271
|
+
|
272
|
+
def to_safe_format
|
273
|
+
dup.to_safe_format!
|
274
|
+
end
|
275
|
+
|
276
|
+
#
|
277
|
+
# Standardise the data for safe export to Excel.
|
278
|
+
# Set each cell contents to a string and remove leading equals signs.
|
279
|
+
#
|
280
|
+
|
281
|
+
def to_safe_format!
|
282
|
+
sheets &:to_safe_format!
|
283
|
+
self
|
284
|
+
end
|
285
|
+
|
286
|
+
end # Workbook
|
287
|
+
|
262
288
|
end # RubyExcel
|