robust_excel_ole 0.3.7 → 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|