robust_excel_ole 0.3.4 → 0.3.5
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.
- 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
|