robust_excel_ole 0.3.7 → 0.3.8
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/Changelog +29 -0
- data/README.rdoc +12 -12
- data/README_detail.rdoc +3 -3
- data/examples/open_save_close/example_simple.rb +6 -1
- data/lib/robust_excel_ole.rb +69 -30
- data/lib/robust_excel_ole/book.rb +128 -64
- data/lib/robust_excel_ole/bookstore.rb +17 -10
- data/lib/robust_excel_ole/cygwin.rb +1 -1
- data/lib/robust_excel_ole/excel.rb +73 -33
- data/lib/robust_excel_ole/sheet.rb +41 -15
- data/lib/robust_excel_ole/utilities.rb +28 -0
- data/lib/robust_excel_ole/version.rb +1 -1
- data/spec/book_specs/book_open_spec.rb +0 -1
- data/spec/data/workbook.xls +0 -0
- metadata +5 -4
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
|
+
include Utilities
|
5
|
+
|
4
6
|
module RobustExcelOle
|
5
7
|
|
6
8
|
class Bookstore
|
@@ -11,6 +13,10 @@ module RobustExcelOle
|
|
11
13
|
end
|
12
14
|
|
13
15
|
# returns a book with the given filename, if it was open once
|
16
|
+
# @param [String] filename the file name
|
17
|
+
# @param [Hash] options the options
|
18
|
+
# @option option [Boolean] :prefer_writable
|
19
|
+
# @option option [Boolean] :prefer_excel
|
14
20
|
# prefers open books to closed books, and among them, prefers more recently opened books
|
15
21
|
# excludes hidden Excel instance
|
16
22
|
# options: :prefer_writable returns the writable book, if it is open (default: true)
|
@@ -29,7 +35,7 @@ module RobustExcelOle
|
|
29
35
|
begin
|
30
36
|
@filename2books[filename_key].delete(wr_book)
|
31
37
|
rescue
|
32
|
-
|
38
|
+
trace "Warning: deleting dead reference failed: file: #{filename.inspect}"
|
33
39
|
end
|
34
40
|
else
|
35
41
|
book = wr_book.__getobj__
|
@@ -51,6 +57,7 @@ module RobustExcelOle
|
|
51
57
|
end
|
52
58
|
|
53
59
|
# stores a workbook
|
60
|
+
# @param [Book] book a given book
|
54
61
|
def store(book)
|
55
62
|
filename_key = RobustExcelOle::canonize(book.filename)
|
56
63
|
if book.stored_filename
|
@@ -63,7 +70,7 @@ module RobustExcelOle
|
|
63
70
|
end
|
64
71
|
|
65
72
|
# creates and returns a separate Excel instance with Visible and DisplayAlerts equal false
|
66
|
-
def hidden_excel
|
73
|
+
def hidden_excel # :nodoc: #
|
67
74
|
unless (@hidden_excel_instance && @hidden_excel_instance.weakref_alive? && @hidden_excel_instance.__getobj__.alive?)
|
68
75
|
@hidden_excel_instance = WeakRef.new(Excel.create)
|
69
76
|
end
|
@@ -87,25 +94,25 @@ module RobustExcelOle
|
|
87
94
|
|
88
95
|
private
|
89
96
|
|
90
|
-
def try_hidden_excel
|
97
|
+
def try_hidden_excel # :nodoc: #
|
91
98
|
@hidden_excel_instance.__getobj__ if (@hidden_excel_instance && @hidden_excel_instance.weakref_alive? && @hidden_excel_instance.__getobj__.alive?)
|
92
99
|
end
|
93
100
|
|
94
101
|
# prints the book store
|
95
|
-
def print
|
96
|
-
|
102
|
+
def print # :nodoc: #
|
103
|
+
trace "@filename2books:"
|
97
104
|
if @filename2books
|
98
105
|
@filename2books.each do |filename,books|
|
99
|
-
|
100
|
-
|
106
|
+
trace " filename: #{filename}"
|
107
|
+
trace " books:"
|
101
108
|
if books.empty?
|
102
|
-
|
109
|
+
trace " []"
|
103
110
|
else
|
104
111
|
books.each do |book|
|
105
112
|
if book.weakref_alive?
|
106
|
-
|
113
|
+
trace "#{book}"
|
107
114
|
else
|
108
|
-
|
115
|
+
trace "weakref not alive"
|
109
116
|
end
|
110
117
|
end
|
111
118
|
end
|
@@ -11,7 +11,7 @@ module RobustExcelOle
|
|
11
11
|
@conv_to_win32_path =
|
12
12
|
Win32API.new('cygwin1.dll', 'cygwin_conv_to_win32_path', 'PP', 'I')
|
13
13
|
|
14
|
-
def cygpath(options, path)
|
14
|
+
def cygpath(options, path) # :nodoc: #
|
15
15
|
absolute = shortname = false
|
16
16
|
func = nil
|
17
17
|
options.delete(" \t-").chars {|opt|
|
@@ -2,30 +2,40 @@
|
|
2
2
|
|
3
3
|
require 'timeout'
|
4
4
|
|
5
|
-
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
module RobustExcelOle
|
6
8
|
|
7
9
|
class Excel
|
8
10
|
|
9
11
|
@@hwnd2excel = {}
|
10
12
|
|
11
13
|
# creates a new Excel instance
|
14
|
+
# @return [Excel] a new Excel instance
|
12
15
|
def self.create
|
13
16
|
new(:reuse => false)
|
14
17
|
end
|
15
18
|
|
16
19
|
# uses the current Excel instance (connects), if such a running Excel instance exists
|
17
20
|
# creates a new one, otherwise
|
21
|
+
# @return [Excel] an Excel instance
|
18
22
|
def self.current
|
19
23
|
new(:reuse => true)
|
20
24
|
end
|
21
25
|
|
22
26
|
# returns an Excel instance
|
23
|
-
# given
|
27
|
+
# given a WIN32OLE object representing an Excel instance, or a Hash representing options:
|
28
|
+
# @param [Hash] options the options
|
29
|
+
# @option options [Boolean] :reuse
|
30
|
+
# @option options [Boolean] :displayalerts
|
31
|
+
# @option options [Boolean] :visible
|
32
|
+
# options:
|
24
33
|
# :reuse connects to an already running Excel instance (true) or
|
25
34
|
# creates a new Excel instance (false) (default: true)
|
26
35
|
# :displayalerts allows display alerts in Excel (default: false)
|
27
36
|
# :visible makes the Excel visible (default: false)
|
28
37
|
# if :reuse => true, then DisplayAlerts and Visible are set only if they are given
|
38
|
+
# @return [Excel] an Excel instance
|
29
39
|
def self.new(options = {})
|
30
40
|
if options.is_a? WIN32OLE
|
31
41
|
excel = options
|
@@ -66,8 +76,13 @@ module RobustExcelOle
|
|
66
76
|
end
|
67
77
|
|
68
78
|
# reopens a closed Excel instance
|
79
|
+
# @param [Hash] opts the options
|
80
|
+
# @option opts [Boolean] :reopen_workbooks
|
81
|
+
# @option opts [Boolean] :displayalerts
|
82
|
+
# @option opts [Boolean] :visible
|
69
83
|
# options: reopen_workbooks (default: false): reopen the workbooks in the Excel instances
|
70
84
|
# :visible (default: false), :displayalerts (default: false)
|
85
|
+
# @return [Excel] an Excel instance
|
71
86
|
def recreate(opts = {})
|
72
87
|
unless self.alive?
|
73
88
|
opts = {
|
@@ -99,7 +114,7 @@ module RobustExcelOle
|
|
99
114
|
begin
|
100
115
|
result.Visible # send any method, just to see if it responds
|
101
116
|
rescue
|
102
|
-
|
117
|
+
trace "dead excel " + ("Window-handle = #{result.HWnd}" rescue "without window handle")
|
103
118
|
return nil
|
104
119
|
end
|
105
120
|
end
|
@@ -110,14 +125,21 @@ module RobustExcelOle
|
|
110
125
|
public
|
111
126
|
|
112
127
|
# closes all Excel instances
|
128
|
+
# @param [Hash] options the options
|
129
|
+
# @option options [Symbol] :if_unsaved :raise, :save, :forget, :alert, or :keep_open
|
130
|
+
# @option options [Boolean] :hard
|
131
|
+
# @option options [Boolean] :kill_if_timeout
|
113
132
|
# options:
|
114
133
|
# :if_unsaved if unsaved workbooks are open in an Excel instance
|
115
134
|
# :raise (default) -> raises an exception
|
116
135
|
# :save -> saves the workbooks before closing
|
117
136
|
# :forget -> closes the excel instance without saving the workbooks
|
137
|
+
# :keep_open -> let the workbooks open
|
118
138
|
# :alert -> give control to Excel
|
119
139
|
# :hard closes Excel instances soft (default: false), or, additionally kills the Excel processes hard (true)
|
120
140
|
# :kill_if_timeout: kills Excel instances hard if the closing process exceeds a certain time limit (default: true)
|
141
|
+
# @raise ExcelError if time limit has exceeded, some Excel instance cannot be closed, or
|
142
|
+
# unsaved workbooks exist and option :if_unsaved is :raise
|
121
143
|
def self.close_all(options={})
|
122
144
|
options = {
|
123
145
|
:if_unsaved => :raise,
|
@@ -153,12 +175,12 @@ module RobustExcelOle
|
|
153
175
|
|
154
176
|
private
|
155
177
|
|
156
|
-
def self.manage_unsaved_workbooks(excel, options)
|
178
|
+
def self.manage_unsaved_workbooks(excel, options) # :nodoc: #
|
157
179
|
unsaved_workbooks = []
|
158
180
|
begin
|
159
181
|
excel.Workbooks.each {|w| unsaved_workbooks << w unless (w.Saved || w.ReadOnly)}
|
160
182
|
rescue RuntimeError => msg
|
161
|
-
|
183
|
+
trace "RuntimeError: #{msg.message}"
|
162
184
|
raise ExcelErrorOpen, "Excel instance not alive or damaged" if msg.message =~ /failed to get Dispatch Interface/
|
163
185
|
end
|
164
186
|
unless unsaved_workbooks.empty?
|
@@ -199,7 +221,7 @@ module RobustExcelOle
|
|
199
221
|
end
|
200
222
|
end
|
201
223
|
|
202
|
-
def self.close_excel_ole_instance(ole_excel)
|
224
|
+
def self.close_excel_ole_instance(ole_excel) # :nodoc: #
|
203
225
|
@@hwnd2excel.delete(ole_excel.Hwnd)
|
204
226
|
excel = ole_excel
|
205
227
|
ole_excel = nil
|
@@ -214,21 +236,21 @@ module RobustExcelOle
|
|
214
236
|
if weak_excel_ref.weakref_alive? then
|
215
237
|
begin
|
216
238
|
weak_excel_ref.ole_free
|
217
|
-
|
239
|
+
trace "successfully ole_freed #{weak_excel_ref}"
|
218
240
|
rescue
|
219
|
-
|
241
|
+
trace "could not do ole_free on #{weak_excel_ref}"
|
220
242
|
end
|
221
243
|
end
|
222
244
|
@@hwnd2excel.delete(excel_hwnd)
|
223
245
|
rescue => e
|
224
|
-
|
246
|
+
trace "Error when closing Excel: #{e.message}"
|
225
247
|
#t e.backtrace
|
226
248
|
end
|
227
249
|
free_all_ole_objects
|
228
250
|
end
|
229
251
|
|
230
252
|
# frees all OLE objects in the object space
|
231
|
-
def self.free_all_ole_objects
|
253
|
+
def self.free_all_ole_objects # :nodoc: #
|
232
254
|
anz_objekte = 0
|
233
255
|
ObjectSpace.each_object(WIN32OLE) do |o|
|
234
256
|
anz_objekte += 1
|
@@ -238,18 +260,21 @@ module RobustExcelOle
|
|
238
260
|
#t o.ole_type rescue nil
|
239
261
|
begin
|
240
262
|
o.ole_free
|
241
|
-
#
|
263
|
+
#trace "olefree OK"
|
242
264
|
rescue
|
243
|
-
#
|
244
|
-
#
|
265
|
+
#trace "olefree_error: #{$!}"
|
266
|
+
#trace $!.backtrace.first(9).join "\n"
|
245
267
|
end
|
246
268
|
end
|
247
|
-
|
269
|
+
trace "went through #{anz_objekte} OLE objects"
|
248
270
|
end
|
249
271
|
|
250
272
|
public
|
251
273
|
|
252
274
|
# closes the Excel
|
275
|
+
# @param [Hash] options the options
|
276
|
+
# @option options [Symbol] :if_unsaved :raise, :save, :forget, or :keep_open
|
277
|
+
# @option options [Boolean] :hard
|
253
278
|
# :if_unsaved if unsaved workbooks are open in an Excel instance
|
254
279
|
# :raise (default) -> raises an exception
|
255
280
|
# :save -> saves the workbooks before closing
|
@@ -285,10 +310,10 @@ module RobustExcelOle
|
|
285
310
|
if weak_excel_ref.weakref_alive? then
|
286
311
|
begin
|
287
312
|
weak_excel_ref.ole_free
|
288
|
-
|
313
|
+
trace "successfully ole_freed #{weak_excel_ref}"
|
289
314
|
rescue => msg
|
290
|
-
|
291
|
-
|
315
|
+
trace "#{msg.message}"
|
316
|
+
trace "could not do ole_free on #{weak_excel_ref}"
|
292
317
|
end
|
293
318
|
end
|
294
319
|
@@hwnd2excel.delete(excel_hwnd)
|
@@ -305,6 +330,7 @@ module RobustExcelOle
|
|
305
330
|
public
|
306
331
|
|
307
332
|
# kill all Excel instances
|
333
|
+
# @return [Fixnum] number of killed Excel processes
|
308
334
|
def self.kill_all
|
309
335
|
procs = WIN32OLE.connect("winmgmts:\\\\.")
|
310
336
|
processes = procs.InstancesOf("win32_process")
|
@@ -345,31 +371,31 @@ module RobustExcelOle
|
|
345
371
|
result
|
346
372
|
end
|
347
373
|
|
348
|
-
def excel
|
374
|
+
def excel # :nodoc: #
|
349
375
|
self
|
350
376
|
end
|
351
377
|
|
352
|
-
def self.hwnd2excel(hwnd)
|
378
|
+
def self.hwnd2excel(hwnd) # :nodoc: #
|
353
379
|
excel_weakref = @@hwnd2excel[hwnd]
|
354
380
|
if excel_weakref
|
355
381
|
if excel_weakref.weakref_alive?
|
356
382
|
excel_weakref.__getobj__
|
357
383
|
else
|
358
|
-
|
384
|
+
trace "dead reference to an Excel"
|
359
385
|
begin
|
360
386
|
@@hwnd2excel.delete(hwnd)
|
361
387
|
rescue
|
362
|
-
|
388
|
+
trace "Warning: deleting dead reference failed! (hwnd: #{hwnd.inspect})"
|
363
389
|
end
|
364
390
|
end
|
365
391
|
end
|
366
392
|
end
|
367
393
|
|
368
|
-
def hwnd
|
394
|
+
def hwnd # :nodoc: #
|
369
395
|
self.Hwnd rescue nil
|
370
396
|
end
|
371
397
|
|
372
|
-
def self.print_hwnd2excel
|
398
|
+
def self.print_hwnd2excel # :nodoc: #
|
373
399
|
@@hwnd2excel.each do |hwnd,wr_excel|
|
374
400
|
excel_string = (wr_excel.weakref_alive? ? wr_excel.__getobj__.to_s : "weakref not alive")
|
375
401
|
printf("hwnd: %8i => excel: %s\n", hwnd, excel_string)
|
@@ -393,7 +419,7 @@ module RobustExcelOle
|
|
393
419
|
|
394
420
|
|
395
421
|
# returns all unsaved workbooks in Excel instances
|
396
|
-
def self.unsaved_workbooks_all
|
422
|
+
def self.unsaved_workbooks_all # :nodoc: #
|
397
423
|
result = []
|
398
424
|
@@hwnd2excel.each do |hwnd,wr_excel|
|
399
425
|
excel = wr_excel.__getobj__
|
@@ -408,7 +434,7 @@ module RobustExcelOle
|
|
408
434
|
begin
|
409
435
|
self.Workbooks.each {|w| result << w unless (w.Saved || w.ReadOnly)}
|
410
436
|
rescue RuntimeError => msg
|
411
|
-
|
437
|
+
trace "RuntimeError: #{msg.message}"
|
412
438
|
raise ExcelErrorOpen, "Excel instance not alive or damaged" if msg.message =~ /failed to get Dispatch Interface/
|
413
439
|
end
|
414
440
|
result
|
@@ -417,11 +443,11 @@ module RobustExcelOle
|
|
417
443
|
#self.class.map {|w| (not w.Saved)}
|
418
444
|
|
419
445
|
def print_workbooks
|
420
|
-
self.Workbooks.each {|w|
|
446
|
+
self.Workbooks.each {|w| puts "#{w.Name} #{w}"}
|
421
447
|
end
|
422
448
|
|
423
449
|
|
424
|
-
#
|
450
|
+
# generates, saves, and closes empty workbook
|
425
451
|
def generate_workbook file_name
|
426
452
|
self.Workbooks.Add
|
427
453
|
empty_workbook = self.Workbooks.Item(self.Workbooks.Count)
|
@@ -457,7 +483,7 @@ module RobustExcelOle
|
|
457
483
|
@displayalerts = @ole_excel.DisplayAlerts = displayalerts_value
|
458
484
|
end
|
459
485
|
|
460
|
-
# return
|
486
|
+
# return whether DisplayAlerts is enabled in the current Excel instance
|
461
487
|
def displayalerts
|
462
488
|
@displayalerts = @ole_excel.DisplayAlerts
|
463
489
|
end
|
@@ -472,15 +498,15 @@ module RobustExcelOle
|
|
472
498
|
@visible = @ole_excel.Visible
|
473
499
|
end
|
474
500
|
|
475
|
-
def to_s
|
501
|
+
def to_s # :nodoc: #
|
476
502
|
"#<Excel: " + "#{hwnd}" + ("#{"not alive" unless self.alive?}") + ">"
|
477
503
|
end
|
478
504
|
|
479
|
-
def inspect
|
505
|
+
def inspect # :nodoc: #
|
480
506
|
self.to_s
|
481
507
|
end
|
482
508
|
|
483
|
-
def self.book_class
|
509
|
+
def self.book_class # :nodoc: #
|
484
510
|
@book_class ||= begin
|
485
511
|
module_name = self.parent_name
|
486
512
|
"#{module_name}::Book".constantize
|
@@ -489,13 +515,27 @@ module RobustExcelOle
|
|
489
515
|
end
|
490
516
|
end
|
491
517
|
|
492
|
-
def book_class
|
518
|
+
def book_class # :nodoc: #
|
493
519
|
self.class.book_class
|
494
520
|
end
|
495
521
|
|
522
|
+
def respond_to?(name, include_private = false) # :nodoc: #
|
523
|
+
raise ExcelError, "respond_to?: Excel not alive" unless alive?
|
524
|
+
super
|
525
|
+
end
|
526
|
+
|
527
|
+
def methods # :nodoc: #
|
528
|
+
(super + @ole_excel.ole_methods.map{|m| m.to_s}).uniq
|
529
|
+
end
|
530
|
+
|
531
|
+
def special_methods # :nodoc: #
|
532
|
+
(methods - Object.methods).sort
|
533
|
+
end
|
534
|
+
|
535
|
+
|
496
536
|
private
|
497
537
|
|
498
|
-
def method_missing(name, *args)
|
538
|
+
def method_missing(name, *args) # :nodoc: #
|
499
539
|
if name.to_s[0,1] =~ /[A-Z]/
|
500
540
|
begin
|
501
541
|
raise ExcelError, "method missing: Excel not alive" unless alive?
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
include Utilities
|
4
|
+
|
2
5
|
module RobustExcelOle
|
6
|
+
|
3
7
|
class Sheet
|
4
8
|
attr_reader :worksheet
|
5
9
|
|
@@ -16,10 +20,13 @@ module RobustExcelOle
|
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
23
|
+
# returns name of the workbook
|
19
24
|
def name
|
20
25
|
@worksheet.Name
|
21
26
|
end
|
22
27
|
|
28
|
+
# name the sheet
|
29
|
+
# @param [String] new_name the new name of the sheet
|
23
30
|
def name= (new_name)
|
24
31
|
begin
|
25
32
|
@worksheet.Name = new_name
|
@@ -27,7 +34,7 @@ module RobustExcelOle
|
|
27
34
|
if msg.message =~ /800A03EC/
|
28
35
|
raise ExcelErrorSheet, "sheet name #{new_name.inspect} already exists"
|
29
36
|
else
|
30
|
-
|
37
|
+
trace "#{msg.message}"
|
31
38
|
raise ExcelErrorSheetUnknown
|
32
39
|
end
|
33
40
|
end
|
@@ -104,8 +111,12 @@ module RobustExcelOle
|
|
104
111
|
end
|
105
112
|
|
106
113
|
# returns the contents of a range with given name
|
114
|
+
# @param [String] name the range name
|
115
|
+
# @param [Hash] opts the options
|
116
|
+
# @option opts [Variant] :default default value (default: nil)
|
107
117
|
# if no contents could returned, then return default value, if a default value was provided
|
108
118
|
# raise an error, otherwise
|
119
|
+
# @raise SheetError if value of the range cannot be evaluated
|
109
120
|
def nvalue(name, opts = {:default => nil})
|
110
121
|
begin
|
111
122
|
value = self.Evaluate(name)
|
@@ -116,12 +127,16 @@ module RobustExcelOle
|
|
116
127
|
end
|
117
128
|
if value == -2146826259
|
118
129
|
return opts[:default] if opts[:default]
|
119
|
-
raise
|
130
|
+
raise SheeetError, "cannot evaluate name #{name.inspect} in sheet"
|
120
131
|
end
|
121
132
|
return opts[:default] if (value.nil? && opts[:default])
|
122
133
|
value
|
123
134
|
end
|
124
135
|
|
136
|
+
# assigns a value to a range with given name
|
137
|
+
# @param [String] name the range name
|
138
|
+
# @param [Variant] value the assigned value
|
139
|
+
# @raise SheetError if name is not in the sheet or the value cannot be assigned
|
125
140
|
def set_nvalue(name,value)
|
126
141
|
begin
|
127
142
|
item = self.Names.Item(name)
|
@@ -136,6 +151,9 @@ module RobustExcelOle
|
|
136
151
|
end
|
137
152
|
|
138
153
|
# assigns a name to a range (a cell) given by an address
|
154
|
+
# @param [String] name the range name
|
155
|
+
# @param [Fixnum] row the row
|
156
|
+
# @param [Fixnum] column the column
|
139
157
|
def set_name(name,row,column)
|
140
158
|
begin
|
141
159
|
old_name = self[row,column].Name.Name rescue nil
|
@@ -146,28 +164,36 @@ module RobustExcelOle
|
|
146
164
|
self.Names.Add("Name" => name, "RefersToR1C1" => "=" + address)
|
147
165
|
end
|
148
166
|
rescue WIN32OLERuntimeError => msg
|
149
|
-
|
167
|
+
trace "WIN32OLERuntimeError: #{msg.message}"
|
150
168
|
raise SheetError, "cannot add name #{name.inspect} to cell with row #{row.inspect} and column #{column.inspect}"
|
151
169
|
end
|
152
170
|
end
|
153
171
|
|
172
|
+
def respond_to?(name, include_private = false) # :nodoc: #
|
173
|
+
super
|
174
|
+
end
|
175
|
+
|
176
|
+
def methods # :nodoc: #
|
177
|
+
super
|
178
|
+
end
|
179
|
+
|
154
180
|
private
|
155
181
|
|
156
|
-
def method_missing(name, *args)
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
182
|
+
def method_missing(name, *args) # :nodoc: #
|
183
|
+
if name.to_s[0,1] =~ /[A-Z]/
|
184
|
+
begin
|
185
|
+
@worksheet.send(name, *args)
|
186
|
+
rescue WIN32OLERuntimeError => msg
|
187
|
+
if msg.message =~ /unknown property or method/
|
188
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
189
|
+
else
|
190
|
+
raise msg
|
191
|
+
end
|
165
192
|
end
|
193
|
+
else
|
194
|
+
super
|
166
195
|
end
|
167
|
-
else
|
168
|
-
super
|
169
196
|
end
|
170
|
-
end
|
171
197
|
|
172
198
|
|
173
199
|
def last_row
|