robust_excel_ole 0.3.4 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +73 -26
- data/README_detail.rdoc +92 -27
- data/examples/edit_sheets/example_access_sheets_and_cells.rb +3 -3
- data/examples/edit_sheets/example_concating.rb +12 -12
- data/examples/edit_sheets/example_copying.rb +47 -0
- data/examples/edit_sheets/example_expanding.rb +17 -26
- data/examples/edit_sheets/example_naming.rb +13 -10
- data/examples/edit_sheets/example_ranges.rb +2 -2
- data/examples/edit_sheets/example_saving.rb +8 -14
- data/examples/open_save_close/example_control_to_excel.rb +1 -1
- data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +3 -3
- data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
- data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
- data/examples/open_save_close/example_if_unsaved_forget.rb +4 -4
- data/examples/open_save_close/example_if_unsaved_forget_more.rb +5 -5
- data/examples/open_save_close/example_read_only.rb +1 -1
- data/examples/open_save_close/example_rename_cells.rb +1 -13
- data/examples/open_save_close/example_simple.rb +1 -1
- data/examples/open_save_close/example_unobtrusively.rb +3 -3
- data/lib/robust_excel_ole.rb +81 -2
- data/lib/robust_excel_ole/book.rb +171 -118
- data/lib/robust_excel_ole/{book_store.rb → bookstore.rb} +2 -2
- data/lib/robust_excel_ole/excel.rb +153 -24
- data/lib/robust_excel_ole/range.rb +2 -2
- data/lib/robust_excel_ole/sheet.rb +84 -35
- data/lib/robust_excel_ole/version.rb +1 -1
- data/reo.bat +3 -0
- data/spec/book_close_spec.rb +179 -0
- data/spec/book_misc_spec.rb +365 -0
- data/spec/book_open_spec.rb +793 -0
- data/spec/book_save_spec.rb +257 -0
- data/spec/book_sheet_spec.rb +160 -0
- data/spec/book_spec.rb +145 -1533
- data/spec/book_subclass_spec.rb +50 -0
- data/spec/book_unobtr_spec.rb +950 -0
- data/spec/{book_store_spec.rb → bookstore_spec.rb} +5 -5
- data/spec/cell_spec.rb +6 -6
- data/spec/data/{more_workbook.xls → another_workbook.xls} +0 -0
- data/spec/data/different_workbook.xls +0 -0
- data/spec/data/workbook.xls +0 -0
- data/spec/data/workbook.xlsm +0 -0
- data/spec/data/workbook.xlsx +0 -0
- data/spec/data/workbook_linked.xlsm +0 -0
- data/spec/data/workbook_linked_sub.xlsm +0 -0
- data/spec/excel_spec.rb +204 -5
- data/spec/range_spec.rb +6 -6
- data/spec/sheet_spec.rb +122 -34
- metadata +18 -8
- data/spec/data/workbook_connected_sub.xlsm +0 -0
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
module RobustExcelOle
|
5
5
|
|
6
|
-
class
|
6
|
+
class Bookstore
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@filename2books = Hash.new {|hash, key| hash[key] = [] }
|
@@ -99,7 +99,7 @@ module RobustExcelOle
|
|
99
99
|
|
100
100
|
end
|
101
101
|
|
102
|
-
class
|
102
|
+
class BookstoreError < WIN32OLERuntimeError # :nodoc: #
|
103
103
|
end
|
104
104
|
|
105
105
|
end
|
@@ -6,16 +6,6 @@ module RobustExcelOle
|
|
6
6
|
|
7
7
|
@@hwnd2excel = {}
|
8
8
|
|
9
|
-
# closes all Excel instances
|
10
|
-
def self.close_all
|
11
|
-
while current_excel do
|
12
|
-
close_one_excel
|
13
|
-
GC.start
|
14
|
-
sleep 0.3
|
15
|
-
#free_all_ole_objects
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
9
|
# creates a new Excel instance
|
20
10
|
def self.create
|
21
11
|
new(:reuse => false)
|
@@ -55,7 +45,7 @@ module RobustExcelOle
|
|
55
45
|
result = stored
|
56
46
|
else
|
57
47
|
result = super(options)
|
58
|
-
result.instance_variable_set(:@
|
48
|
+
result.instance_variable_set(:@this_excel, excel)
|
59
49
|
WIN32OLE.const_load(excel, RobustExcelOle) unless RobustExcelOle.const_defined?(:CONSTANTS)
|
60
50
|
@@hwnd2excel[hwnd] = result
|
61
51
|
end
|
@@ -63,6 +53,107 @@ module RobustExcelOle
|
|
63
53
|
end
|
64
54
|
|
65
55
|
def initialize(options= {}) # :nodoc: #
|
56
|
+
@excel = self
|
57
|
+
end
|
58
|
+
|
59
|
+
# closes all Excel instances
|
60
|
+
# options:
|
61
|
+
# :if_unsaved if unsaved workbooks are open in an Excel instance
|
62
|
+
# :raise (default) -> raise an exception
|
63
|
+
# :save -> save the workbooks before closing
|
64
|
+
# :forget -> close the excel instance without saving the workbooks
|
65
|
+
# :alert -> give control to Excel
|
66
|
+
# :hard kill the Excel instances hard (default: false)
|
67
|
+
def self.close_all(options={})
|
68
|
+
options = {
|
69
|
+
:if_unsaved => :raise,
|
70
|
+
:hard => false
|
71
|
+
}.merge(options)
|
72
|
+
while current_excel do
|
73
|
+
#current_excel.close(options)
|
74
|
+
close_one_excel
|
75
|
+
GC.start
|
76
|
+
sleep 0.3
|
77
|
+
# free_all_ole_objects if options[:hard] ???
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# close the Excel
|
82
|
+
# :if_unsaved if unsaved workbooks are open in an Excel instance
|
83
|
+
# :raise (default) -> raise an exception
|
84
|
+
# :save -> save the workbooks before closing
|
85
|
+
# :forget -> close the excel instance without saving the workbooks
|
86
|
+
# :alert -> give control to Excel
|
87
|
+
# :hard kill the Excel instance hard (default: false)
|
88
|
+
def close(options = {})
|
89
|
+
options = {
|
90
|
+
:if_unsaved => :raise,
|
91
|
+
:hard => false
|
92
|
+
}.merge(options)
|
93
|
+
unsaved_books = self.unsaved_workbooks
|
94
|
+
unless unsaved_books.empty?
|
95
|
+
case options[:if_unsaved]
|
96
|
+
when :raise
|
97
|
+
raise ExcelErrorClose, "Excel contains unsaved workbooks"
|
98
|
+
when :save
|
99
|
+
unsaved_workbooks.each do |workbook|
|
100
|
+
workbook.Save
|
101
|
+
end
|
102
|
+
close_excel(:hard => options[:hard])
|
103
|
+
when :forget
|
104
|
+
close_excel(:hard => options[:hard])
|
105
|
+
when :alert
|
106
|
+
with_displayalerts true do
|
107
|
+
unsaved_workbooks.each do |workbook|
|
108
|
+
workbook.Save
|
109
|
+
end
|
110
|
+
close_excel(:hard => options[:hard])
|
111
|
+
end
|
112
|
+
else
|
113
|
+
raise ExcelErrorClose, ":if_unsaved: invalid option: #{options[:if_unsaved]}"
|
114
|
+
end
|
115
|
+
else
|
116
|
+
close_excel(:hard => options[:hard])
|
117
|
+
end
|
118
|
+
raise ExcelUserCanceled, "close: canceled by user" if options[:if_unsaved] == :alert && self.unsaved_workbooks
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def close_excel(options)
|
124
|
+
excel = @this_excel
|
125
|
+
excel.Workbooks.Close
|
126
|
+
excel_hwnd = excel.HWnd
|
127
|
+
excel.Quit
|
128
|
+
weak_excel_ref = WeakRef.new(excel)
|
129
|
+
excel = nil
|
130
|
+
GC.start
|
131
|
+
sleep 0.2
|
132
|
+
if weak_excel_ref.weakref_alive? then
|
133
|
+
#if WIN32OLE.ole_reference_count(weak_xlapp) > 0
|
134
|
+
begin
|
135
|
+
weak_excel_ref.ole_free
|
136
|
+
puts "successfully ole_freed #{weak_excel_ref}"
|
137
|
+
rescue
|
138
|
+
puts "could not do ole_free on #{weak_excel_ref}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
hwnd2excel(excel_hwnd).die rescue nil
|
142
|
+
#@@hwnd2excel[excel_hwnd] = nil
|
143
|
+
#Excel.free_all_ole_objects
|
144
|
+
if options[:hard] then
|
145
|
+
process_id = Win32API.new("user32", "GetWindowThreadProcessId", ["I","P"], "I")
|
146
|
+
pid_puffer = " " * 32
|
147
|
+
process_id.call(excel_hwnd, pid_puffer)
|
148
|
+
pid = pid_puffer.unpack("L")[0]
|
149
|
+
Process.kill("KILL", pid)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
public
|
154
|
+
|
155
|
+
def excel
|
156
|
+
self
|
66
157
|
end
|
67
158
|
|
68
159
|
# generate, save and close an empty workbook
|
@@ -100,51 +191,86 @@ module RobustExcelOle
|
|
100
191
|
|
101
192
|
# returns true, if the Excel instances responds to VVA methods, false otherwise
|
102
193
|
def alive?
|
103
|
-
@
|
194
|
+
@this_excel.Name
|
104
195
|
true
|
105
196
|
rescue
|
106
|
-
puts $!.message
|
197
|
+
#puts $!.message
|
107
198
|
false
|
108
199
|
end
|
109
200
|
|
201
|
+
def print_workbooks
|
202
|
+
self.Workbooks.each {|w| puts w.Name}
|
203
|
+
end
|
204
|
+
|
205
|
+
def unsaved_workbooks
|
206
|
+
result = []
|
207
|
+
begin
|
208
|
+
self.Workbooks.each {|w| result << w unless (w.Saved || w.ReadOnly)}
|
209
|
+
rescue RuntimeError => msg
|
210
|
+
puts "RuntimeError: #{msg.message}"
|
211
|
+
raise ExcelErrorOpen, "Excel instance not alive or damaged" if msg.message =~ /failed to get Dispatch Interface/
|
212
|
+
end
|
213
|
+
result
|
214
|
+
end
|
215
|
+
# yields different WIN32OLE objects than book.workbook
|
216
|
+
#self.class.extend Enumerable
|
217
|
+
#self.class.map {|w| (not w.Saved)}
|
218
|
+
|
110
219
|
# set DisplayAlerts in a block
|
111
220
|
def with_displayalerts displayalerts_value
|
112
|
-
old_displayalerts = @
|
113
|
-
@
|
221
|
+
old_displayalerts = @this_excel.DisplayAlerts
|
222
|
+
@this_excel.DisplayAlerts = displayalerts_value
|
114
223
|
begin
|
115
224
|
yield self
|
116
225
|
ensure
|
117
|
-
@
|
226
|
+
@this_excel.DisplayAlerts = old_displayalerts
|
118
227
|
end
|
119
228
|
end
|
120
229
|
|
121
230
|
# enable DisplayAlerts in the current Excel instance
|
122
231
|
def displayalerts= displayalerts_value
|
123
|
-
@
|
232
|
+
@this_excel.DisplayAlerts = displayalerts_value
|
124
233
|
end
|
125
234
|
|
126
235
|
# return if in the current Excel instance DisplayAlerts is enabled
|
127
236
|
def displayalerts
|
128
|
-
@
|
237
|
+
@this_excel.DisplayAlerts
|
129
238
|
end
|
130
239
|
|
131
240
|
# make the current Excel instance visible or invisible
|
132
241
|
def visible= visible_value
|
133
|
-
@
|
242
|
+
@this_excel.Visible = visible_value
|
134
243
|
end
|
135
244
|
|
136
245
|
# return if the current Excel instance is visible
|
137
246
|
def visible
|
138
|
-
@
|
247
|
+
@this_excel.Visible
|
139
248
|
end
|
140
249
|
|
250
|
+
def to_s
|
251
|
+
"#<" + "EXCEL:#{hwnd_xxxx}" + ("#{"not alive" unless self.alive?}") + ">"
|
252
|
+
end
|
253
|
+
|
254
|
+
def inspect
|
255
|
+
self.to_s
|
256
|
+
end
|
141
257
|
|
142
258
|
private
|
143
259
|
|
144
260
|
# closes one Excel instance
|
145
|
-
def self.close_one_excel
|
261
|
+
def self.close_one_excel(options={})
|
146
262
|
excel = current_excel
|
147
263
|
if excel then
|
264
|
+
weak_ole_excel = WeakRef.new(excel)
|
265
|
+
excel = nil
|
266
|
+
close_excel_ole_instance(weak_ole_excel.__getobj__)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.close_excel_ole_instance(ole_excel)
|
271
|
+
excel = ole_excel
|
272
|
+
ole_excel = nil
|
273
|
+
begin
|
148
274
|
excel.Workbooks.Close
|
149
275
|
excel_hwnd = excel.HWnd
|
150
276
|
excel.Quit
|
@@ -165,6 +291,10 @@ module RobustExcelOle
|
|
165
291
|
|
166
292
|
hwnd2excel(excel_hwnd).die rescue nil
|
167
293
|
#@@hwnd2excel[excel_hwnd] = nil
|
294
|
+
|
295
|
+
rescue => e
|
296
|
+
puts "Error when closing Excel: " + e.message
|
297
|
+
#puts e.backtrace
|
168
298
|
end
|
169
299
|
|
170
300
|
|
@@ -220,14 +350,13 @@ module RobustExcelOle
|
|
220
350
|
|
221
351
|
# set this Excel instance to nil
|
222
352
|
def die
|
223
|
-
@
|
353
|
+
@this_excel = nil
|
224
354
|
end
|
225
355
|
|
226
|
-
|
227
356
|
def method_missing(name, *args)
|
228
357
|
if name.to_s[0,1] =~ /[A-Z]/
|
229
358
|
begin
|
230
|
-
@
|
359
|
+
@this_excel.send(name, *args)
|
231
360
|
rescue WIN32OLERuntimeError => msg
|
232
361
|
if msg.message =~ /unknown property or method/
|
233
362
|
raise VBAMethodMissingError, "unknown VBA property or method #{name}"
|
@@ -27,8 +27,8 @@ module RobustExcelOle
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def [] index
|
30
|
-
@cells
|
31
|
-
@cells[index + 1]
|
30
|
+
@cells = []
|
31
|
+
@cells[index + 1] = RobustExcelOle::Cell.new(@range.Cells.Item(index + 1))
|
32
32
|
end
|
33
33
|
|
34
34
|
def method_missing(id, *args) # :nodoc: #
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
module RobustExcelOle
|
3
3
|
class Sheet
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :worksheet
|
5
5
|
include Enumerable
|
6
6
|
|
7
7
|
def initialize(win32_worksheet)
|
8
|
-
@
|
9
|
-
if @
|
10
|
-
@
|
8
|
+
@worksheet = win32_worksheet
|
9
|
+
if @worksheet.ProtectContents
|
10
|
+
@worksheet.Unprotect
|
11
11
|
@end_row = last_row
|
12
12
|
@end_column = last_column
|
13
|
-
@
|
13
|
+
@worksheet.Protect
|
14
14
|
else
|
15
15
|
@end_row = last_row
|
16
16
|
@end_column = last_column
|
@@ -18,21 +18,44 @@ module RobustExcelOle
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def name
|
21
|
-
@
|
21
|
+
@worksheet.Name
|
22
22
|
end
|
23
23
|
|
24
24
|
def name= (new_name)
|
25
|
-
|
25
|
+
begin
|
26
|
+
@worksheet.Name = new_name
|
27
|
+
rescue WIN32OLERuntimeError => msg
|
28
|
+
if msg.message =~ /800A03EC/
|
29
|
+
raise ExcelErrorSheet, "sheet name already exists"
|
30
|
+
else
|
31
|
+
puts "#{msg.message}"
|
32
|
+
raise ExcelErrorSheetUnknown
|
33
|
+
end
|
34
|
+
end
|
26
35
|
end
|
27
36
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
37
|
+
# return the value of a cell, if row and column, or its name are given
|
38
|
+
def [] p1, p2 = :__not_provided
|
39
|
+
if p2 != :__not_provided
|
40
|
+
y, x = p1, p2
|
41
|
+
yx = "#{y}_#{x}"
|
42
|
+
@cells = { }
|
43
|
+
@cells[yx] = RobustExcelOle::Cell.new(@worksheet.Cells.Item(y, x))
|
44
|
+
else
|
45
|
+
name = p1
|
46
|
+
nvalue(name)
|
47
|
+
end
|
32
48
|
end
|
33
49
|
|
34
|
-
|
35
|
-
|
50
|
+
# set the value of a cell, if row and column, or its name are given
|
51
|
+
def []= (p1, p2, p3 = :__not_provided)
|
52
|
+
if p3 != :__not_provided
|
53
|
+
y, x, value = p1, p2, p3
|
54
|
+
@worksheet.Cells.Item(y, x).Value = value
|
55
|
+
else
|
56
|
+
name, value = p1, p2
|
57
|
+
set_nvalue(name, value)
|
58
|
+
end
|
36
59
|
end
|
37
60
|
|
38
61
|
def each
|
@@ -47,7 +70,7 @@ module RobustExcelOle
|
|
47
70
|
offset += 1
|
48
71
|
1.upto(@end_row) do |row|
|
49
72
|
next if row < offset
|
50
|
-
yield RobustExcelOle::Range.new(@
|
73
|
+
yield RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(row, 1), @worksheet.Cells(row, @end_column)))
|
51
74
|
end
|
52
75
|
end
|
53
76
|
|
@@ -61,7 +84,7 @@ module RobustExcelOle
|
|
61
84
|
offset += 1
|
62
85
|
1.upto(@end_column) do |column|
|
63
86
|
next if column < offset
|
64
|
-
yield RobustExcelOle::Range.new(@
|
87
|
+
yield RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(1, column), @worksheet.Cells(@end_row, column)))
|
65
88
|
end
|
66
89
|
end
|
67
90
|
|
@@ -72,49 +95,78 @@ module RobustExcelOle
|
|
72
95
|
end
|
73
96
|
|
74
97
|
def row_range(row, range = nil)
|
75
|
-
range ||=
|
76
|
-
RobustExcelOle::Range.new(@
|
98
|
+
range ||= 1..@end_column
|
99
|
+
RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(row , range.min ), @worksheet.Cells(row , range.max )))
|
77
100
|
end
|
78
101
|
|
79
102
|
def col_range(col, range = nil)
|
80
|
-
range ||=
|
81
|
-
RobustExcelOle::Range.new(@
|
103
|
+
range ||= 1..@end_row
|
104
|
+
RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(range.min , col ), @worksheet.Cells(range.max , col )))
|
82
105
|
end
|
83
106
|
|
84
|
-
# returns the contents of a range
|
85
|
-
|
107
|
+
# returns the contents of a range with given name
|
108
|
+
# if no contents could returned, then return default value, if a default value was provided
|
109
|
+
# raise an error, otherwise
|
110
|
+
def nvalue(name, opts = {:default => nil})
|
86
111
|
begin
|
87
112
|
item = self.Names.Item(name)
|
88
113
|
rescue WIN32OLERuntimeError
|
89
|
-
|
114
|
+
return opts[:default] if opts[:default]
|
115
|
+
raise SheetError, "name #{name} not in sheet"
|
90
116
|
end
|
91
117
|
begin
|
92
|
-
|
93
|
-
rescue
|
94
|
-
|
118
|
+
value = item.RefersToRange.Value
|
119
|
+
rescue WIN32OLERuntimeError
|
120
|
+
return opts[:default] if opts[:default]
|
121
|
+
raise SheetError, "RefersToRange of name #{name}"
|
95
122
|
end
|
123
|
+
value
|
124
|
+
end
|
125
|
+
|
126
|
+
def set_nvalue(name,value)
|
96
127
|
begin
|
97
|
-
|
128
|
+
item = self.Names.Item(name)
|
98
129
|
rescue WIN32OLERuntimeError
|
99
|
-
raise
|
130
|
+
raise SheetError, "name #{name} not in sheet"
|
131
|
+
end
|
132
|
+
begin
|
133
|
+
item.RefersToRange.Value = value
|
134
|
+
rescue WIN32OLERuntimeError
|
135
|
+
raise SheetError, "RefersToRange of name #{name}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# assigns a name to a range (a cell) given by an address
|
140
|
+
def set_name(name,row,column)
|
141
|
+
begin
|
142
|
+
old_name = self[row,column].Name.Name rescue nil
|
143
|
+
if old_name
|
144
|
+
self[row,column].Name.Name = name
|
145
|
+
else
|
146
|
+
address = "Z" + row.to_s + "S" + column.to_s
|
147
|
+
self.Names.Add("Name" => name, "RefersToR1C1" => "=" + address)
|
148
|
+
end
|
149
|
+
rescue WIN32OLERuntimeError => msg
|
150
|
+
puts "WIN32OLERuntimeError: #{msg.message}"
|
151
|
+
raise SheetError, "cannot add name #{name} to cell with row #{row} and column #{column}"
|
100
152
|
end
|
101
153
|
end
|
102
154
|
|
103
155
|
def method_missing(id, *args) # :nodoc: #
|
104
|
-
@
|
156
|
+
@worksheet.send(id, *args)
|
105
157
|
end
|
106
158
|
|
107
159
|
private
|
108
160
|
def last_row
|
109
|
-
special_last_row = @
|
110
|
-
used_last_row = @
|
161
|
+
special_last_row = @worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Row
|
162
|
+
used_last_row = @worksheet.UsedRange.Rows.Count
|
111
163
|
|
112
164
|
special_last_row >= used_last_row ? special_last_row : used_last_row
|
113
165
|
end
|
114
166
|
|
115
167
|
def last_column
|
116
|
-
special_last_column = @
|
117
|
-
used_last_column = @
|
168
|
+
special_last_column = @worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Column
|
169
|
+
used_last_column = @worksheet.UsedRange.Columns.Count
|
118
170
|
|
119
171
|
special_last_column >= used_last_column ? special_last_column : used_last_column
|
120
172
|
end
|
@@ -125,7 +177,4 @@ module RobustExcelOle
|
|
125
177
|
class SheetError < RuntimeError # :nodoc: #
|
126
178
|
end
|
127
179
|
|
128
|
-
class SheetErrorNValue < WIN32OLERuntimeError # :nodoc: #
|
129
|
-
end
|
130
|
-
|
131
180
|
end
|