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.
@@ -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