rubyexcel 0.3.9 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Open or connect to an Excel instance
84
- #
85
- # @param [Boolean] invisible leave Excel invisible if creating a new instance
86
- # @return [WIN32OLE::Excel] the first available Excel application
87
- #
88
-
89
- def get_excel( invisible = false )
90
- excel = WIN32OLE::connect( 'excel.application' ) rescue WIN32OLE::new( 'excel.application' )
91
- excel.visible = true unless invisible
92
- excel
93
- end
94
-
95
- #
96
- # Create a new Excel Workbook
97
- #
98
- # @param [WIN32OLE::Excel, nil] excel an Excel object to use
99
- # @param [Boolean] invisible leave Excel invisible if creating a new instance
100
- # @return [WIN32OLE::Workbook] the new Excel Workbook
101
- #
102
-
103
- def get_workbook( excel=nil, invisible = false )
104
- excel ||= get_excel( invisible )
105
- wb = excel.workbooks.add
106
- ( ( wb.sheets.count.to_i ) - 1 ).times { |time| wb.sheets(2).delete }
107
- wb
108
- end
109
-
110
- #
111
- # Import a WIN32OLE Object as a Workbook or Sheet
112
- #
113
- # @param [WIN32OLE::Workbook, WIN32OLE::Sheet, String] other The WIN32OLE Object, either Sheet or Workbook, to import, or a path to the file.
114
- # @param [String] sheetname the name of a specific Sheet to import.
115
- # @param [Boolean] keep_formulas Retain Excel formulas rather than importing their current values
116
- # @return [self] self with the data and name(s) imported.
117
- #
118
-
119
- def import( other, sheetname=nil, keep_formulas=false )
120
- operation = ( keep_formulas ? :formula : :value )
121
-
122
- if other.is_a?( String )
123
-
124
- # Filename
125
- File.exists?( other ) || fail( ArgumentError, "Unable to find file: #{ other }" )
126
-
127
- #Open Excel
128
- excel = WIN32OLE.new( 'excel.application' )
129
- excel.displayalerts = false
130
-
131
- # Open the file
132
- begin
133
- wb = excel.workbooks.open({'filename'=> other, 'readOnly' => true, 'UpdateLinks' => false})
134
- rescue WIN32OLERuntimeError
135
- excel.quit
136
- raise
137
- end
138
-
139
- # Only one sheet, or the entire Workbook?
140
- if sheetname
141
-
142
- add( sheetname ).load( wb.sheets( sheetname ).usedrange.send( operation ) )
143
-
144
- else
145
-
146
- self.name = File.basename( other, '.*' )
147
- wb.sheets.each { |sh| add( sh.name ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? } }
148
-
149
- end
150
-
151
- # Cleanup
152
- wb.close
153
- excel.quit
154
-
155
- elsif !other.respond_to?( :ole_respond_to? )
156
-
157
- fail ArgumentError, "Invalid input: #{other.class}"
158
-
159
- elsif other.ole_respond_to?( :sheets )
160
-
161
- # Workbook
162
-
163
- # Only one sheet, or the entire Workbook?
164
- if sheetname
165
- add( sheetname ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? }
166
- else
167
- self.name = File.basename( other.name, '.*' )
168
- other.sheets.each { |sh| add( sh.name ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? } }
169
- end
170
-
171
- elsif other.ole_respond_to?( :usedrange )
172
-
173
- # Sheet
174
- add( other.name ).tap{ |s| s.load( sh.usedrange.send( operation ) ) unless sh.application.worksheetfunction.counta(sh.cells).zero? }
175
-
176
- else
177
-
178
- fail ArgumentError, "Object not recognised as a WIN32OLE Workbook or Sheet.\n#{other.inspect}"
179
-
180
- end
181
-
182
- self
183
- end
184
-
185
- #
186
- # Take an Excel Sheet and standardise some of the formatting
187
- #
188
- # @param [WIN32OLE::Worksheet] sheet the Sheet to add formatting to
189
- # @return [WIN32OLE::Worksheet] the sheet with formatting added
190
- #
191
-
192
- def make_sheet_pretty( sheet )
193
- c = sheet.cells
194
- c.rowheight = 15
195
- c.entireColumn.autoFit
196
- c.horizontalAlignment = -4108
197
- c.verticalAlignment = -4108
198
- sheet.UsedRange.Columns.each { |col| col.ColumnWidth = 30 if col.ColumnWidth > 50 }
199
- RubyExcel.borders( sheet.usedrange, 1, true )
200
- sheet
201
- end
202
-
203
- #
204
- # Save the RubyExcel::Workbook as an Excel Workbook
205
- #
206
- # @param [String] filename the filename to save as
207
- # @param [Boolean] invisible leave Excel invisible if creating a new instance
208
- # @return [WIN32OLE::Workbook] the Workbook, saved as filename.
209
- #
210
-
211
- def save_excel( filename = nil, invisible = false )
212
- filename ||= name
213
- filename = filename.gsub('/','\\')
214
- unless filename.include?('\\')
215
- filename = documents_path + '\\' + filename
216
- end
217
- wb = to_excel( invisible )
218
- wb.saveas filename
219
- wb
220
- end
221
-
222
- #
223
- # Output the RubyExcel::Workbook to Excel
224
- #
225
- # @param [Boolean] invisible leave Excel invisible if creating a new instance
226
- # @return [WIN32OLE::Workbook] the Workbook in Excel
227
- #
228
-
229
- def to_excel( invisible = false )
230
- self.sheets.count == sheets.map(&:name).uniq.length or fail NoMethodError, 'Duplicate sheet name'
231
- wb = get_workbook( nil, true )
232
- wb.parent.displayAlerts = false
233
- first_time = true
234
- each do |s|
235
- sht = ( first_time ? wb.sheets(1) : wb.sheets.add( { 'after' => wb.sheets( wb.sheets.count ) } ) ); first_time = false
236
- sht.name = s.name
237
- make_sheet_pretty( dump_to_sheet( s.to_a, sht ) )
238
- end
239
- wb.sheets(1).select rescue nil
240
- wb.application.visible = true unless invisible
241
- wb
242
- end
243
-
244
- # {Workbook#to_safe_format!}
245
-
246
- def to_safe_format
247
- dup.to_safe_format!
248
- end
249
-
250
- #
251
- # Standardise the data for safe export to Excel.
252
- # Set each cell contents to a string and remove leading equals signs.
253
- #
254
-
255
- def to_safe_format!
256
- sheets &:to_safe_format!
257
- self
258
- end
259
-
260
- end # Workbook
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