rubyexcel 0.3.9 → 0.4.0
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.
- 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
|