robust_excel_ole 0.3.6 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +1 -2
- data/README.rdoc +17 -9
- data/lib/robust_excel_ole.rb +3 -1
- data/lib/robust_excel_ole/book.rb +73 -14
- data/lib/robust_excel_ole/excel.rb +111 -60
- data/lib/robust_excel_ole/version.rb +1 -1
- data/spec/book_specs/book_close_spec.rb +10 -1
- data/spec/book_specs/book_misc_spec.rb +1 -1
- data/spec/book_specs/book_open_spec.rb +96 -8
- data/spec/book_specs/book_save_spec.rb +1 -1
- data/spec/book_specs/book_sheet_spec.rb +1 -1
- data/spec/book_specs/book_spec.rb +56 -10
- data/spec/book_specs/book_subclass_spec.rb +1 -1
- data/spec/book_specs/book_unobtr_spec.rb +1 -1
- data/spec/bookstore_spec.rb +1 -1
- data/spec/data/another_workbook.xls +0 -0
- data/spec/data/different_workbook.xls +0 -0
- data/spec/data/workbook.xls +0 -0
- data/spec/excel_spec.rb +155 -60
- data/spec/robust_excel_ole_spec.rb +1 -1
- data/spec/sheet_spec.rb +1 -1
- metadata +4 -4
data/Changelog
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
-
## [0.3.6] - 2015-10-
|
4
|
+
## [0.3.6] - 2015-10-27
|
5
5
|
|
6
6
|
### Added
|
7
7
|
- Excel#recreate: reopening a closed Excel
|
@@ -15,7 +15,6 @@ All notable changes to this project will be documented in this file.
|
|
15
15
|
- Method missing: error messages for dead objects
|
16
16
|
- trace to stdout or file
|
17
17
|
|
18
|
-
|
19
18
|
### Changed
|
20
19
|
|
21
20
|
## [0.3.5] - 2015-08-13
|
data/README.rdoc
CHANGED
@@ -43,6 +43,8 @@ Options are
|
|
43
43
|
+:default_excel+, +:force_excel+, +:if_absent+, +:if_unsaved+, +:if_obstructed+,
|
44
44
|
+:read_only+, +:visible+, +:displayalerts+.
|
45
45
|
|
46
|
+
Values for +:default_excel+ are +:reuse+, +:new+ or some Excel instance. Values for +:force_excel+ are +:new+ or some Excel instance. Values for +:if_unsaved+ are +:raise+, +:accept+, +:forget+, +:alert+ and +:new_excel+. Values for +:if_obstructed+ are +:raise+, +:save+, +:close_if_saved+, +:forget+, +:alert+ and +:new_excel+. Values for +:if_absent+ are +:raise+ and +:create+.
|
47
|
+
|
46
48
|
Here are a few examples:
|
47
49
|
|
48
50
|
Opening a workbook in the Excel instance where it was opened before, or opening the workbook in a new Excel instance if it was not opened before.
|
@@ -62,11 +64,13 @@ If a workbook is open and a workbook with the same name but in different path sh
|
|
62
64
|
|
63
65
|
book = Book.open('path/workbook.xls', :if_obstructed => :forget)
|
64
66
|
|
67
|
+
Opening linked workbooks for EXCEL 2007 is supported
|
68
|
+
|
65
69
|
=== Closing a workbook.
|
66
70
|
|
67
71
|
book.close
|
68
72
|
|
69
|
-
There is one option: : +:if_unsaved+. Example:
|
73
|
+
There is one option: : +:if_unsaved+. Values for this option are +:raise+, +:save+, +:forget+, +:alert+ and +:keep_open+. Example:
|
70
74
|
|
71
75
|
Closing the workbook and saving it before if it has unsaved changes.
|
72
76
|
|
@@ -89,7 +93,7 @@ An Excel file (or workbook) is represented by a Book object. A Book object is de
|
|
89
93
|
Identity transparence means that the same Book objects refer to the same Excel files, and vice versa.
|
90
94
|
In other words, a Book objects is a proxy of an Excel file.
|
91
95
|
|
92
|
-
===
|
96
|
+
=== Lifting a workbook to a Book object
|
93
97
|
|
94
98
|
A Book object can be created when giving an Excel workbook.
|
95
99
|
|
@@ -324,7 +328,7 @@ Reusing a running Excel instance, making it visible and turning on displayalerts
|
|
324
328
|
|
325
329
|
excel2 = Excel.new(:reuse => true, :visible => true, :displayalerts => true).
|
326
330
|
|
327
|
-
|
331
|
+
Lifting an Excel instance represented as WIN32OLE object to an Excel object
|
328
332
|
|
329
333
|
excel = Excel.new(:reuse => win32ole_object)
|
330
334
|
|
@@ -362,17 +366,21 @@ Turning on and off in a block.
|
|
362
366
|
excel = Excel.current
|
363
367
|
excel.close
|
364
368
|
|
365
|
-
|
369
|
+
The options are +:if_unsaved+ and +:hard+ . Example:
|
366
370
|
|
367
|
-
|
371
|
+
Closing the Excel instance, saving unsaved wrkbooks and terminating the Excel process
|
372
|
+
|
373
|
+
excel.close(:if_unsaved => :save, :hard => true)
|
368
374
|
|
369
375
|
=== Closing all Excel instances.
|
370
376
|
|
371
377
|
Excel.close_all
|
372
378
|
|
373
|
-
|
379
|
+
The options are +:if_unsaved+ and +:hard+ . Values for :if_unsaved+ are +raise+, +save+, +forget+. Example:
|
380
|
+
|
381
|
+
Closing all Excel instances, not saving unsaved workbooks and terminating the Excel processes
|
374
382
|
|
375
|
-
Excel.close_all(:hard => true)
|
383
|
+
Excel.close_all(:if_unsaved => :forget, :hard => :true)
|
376
384
|
|
377
385
|
=== Terminating all Excel processes
|
378
386
|
|
@@ -385,9 +393,9 @@ Reopening the closed Excel instance. This includes reopening all workbooks that
|
|
385
393
|
excel.close
|
386
394
|
excel.recreate
|
387
395
|
|
388
|
-
|
396
|
+
The options are :reopen_workbooks, :visible and :displayalerts.
|
389
397
|
|
390
|
-
excel.recreate(:visible => true, :displayalerts => true)
|
398
|
+
excel.recreate(:reopen_workbooks => true, :visible => true, :displayalerts => true)
|
391
399
|
|
392
400
|
=== Providing Excel instances
|
393
401
|
|
data/lib/robust_excel_ole.rb
CHANGED
@@ -28,7 +28,7 @@ module RobustExcelOle
|
|
28
28
|
else
|
29
29
|
if REO_LOG_DIR.empty?
|
30
30
|
homes = ["HOME", "HOMEPATH"]
|
31
|
-
home = homes.
|
31
|
+
home = homes.find {|h| ENV[h] != nil}
|
32
32
|
reo_log_dir = ENV[home]
|
33
33
|
else
|
34
34
|
reo_log_dir = REO_LOG_DIR
|
@@ -69,7 +69,9 @@ class Object # :nodoc: #
|
|
69
69
|
def excel
|
70
70
|
raise ExcelError, "receiver instance is neither an Excel nor a Book"
|
71
71
|
end
|
72
|
+
end
|
72
73
|
|
74
|
+
class WIN32OLE
|
73
75
|
end
|
74
76
|
|
75
77
|
class ::String # :nodoc: #
|
@@ -5,6 +5,7 @@ require 'weakref'
|
|
5
5
|
module RobustExcelOle
|
6
6
|
|
7
7
|
class Book
|
8
|
+
|
8
9
|
attr_accessor :excel
|
9
10
|
attr_accessor :workbook
|
10
11
|
attr_accessor :stored_filename
|
@@ -67,9 +68,9 @@ module RobustExcelOle
|
|
67
68
|
# if readonly is true, then prefer a book that is given in force_excel if this option is set
|
68
69
|
book = bookstore.fetch(file,
|
69
70
|
:prefer_writable => (not options[:read_only]),
|
70
|
-
:prefer_excel => (options[:read_only] ? options[:force_excel]
|
71
|
+
:prefer_excel => (options[:read_only] ? excel_of(options[:force_excel]) : nil)) rescue nil
|
71
72
|
if book
|
72
|
-
if (((not options[:force_excel]) || (options[:force_excel]
|
73
|
+
if (((not options[:force_excel]) || (excel_of(options[:force_excel]) == book.excel)) &&
|
73
74
|
(not (book.alive? && (not book.saved) && (not options[:if_unsaved] == :accept))))
|
74
75
|
book.options = DEFAULT_OPEN_OPTS.merge(opts)
|
75
76
|
book.ensure_excel(options) unless book.excel.alive?
|
@@ -81,18 +82,23 @@ module RobustExcelOle
|
|
81
82
|
end
|
82
83
|
end
|
83
84
|
end
|
84
|
-
#options[:excel] = options[:force_excel] ? options[:force_excel] : options[:default_excel]
|
85
85
|
new(file, options, &block)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
# creates a Book object for a given workbook or file name
|
90
|
+
# @param [WIN32OLE] workbook a workbook
|
91
|
+
# @options opts [Symbol] see above
|
92
|
+
# @return [Book] a Book object
|
90
93
|
def self.new(workbook, opts={ }, &block)
|
91
|
-
if workbook && workbook.
|
94
|
+
if workbook && (workbook.is_a? WIN32OLE)
|
92
95
|
filename = workbook.Fullname.tr('\\','/') rescue nil
|
93
96
|
if filename
|
94
97
|
book = bookstore.fetch(filename)
|
95
|
-
|
98
|
+
if book && book.alive?
|
99
|
+
book.apply_options(opts)
|
100
|
+
return book
|
101
|
+
end
|
96
102
|
end
|
97
103
|
end
|
98
104
|
super
|
@@ -103,16 +109,15 @@ module RobustExcelOle
|
|
103
109
|
def initialize(file_or_workbook, opts={ }, &block)
|
104
110
|
options = DEFAULT_OPEN_OPTS.merge(opts)
|
105
111
|
options[:excel] = options[:force_excel] ? options[:force_excel] : options[:default_excel]
|
106
|
-
if file_or_workbook.
|
112
|
+
if file_or_workbook.is_a? WIN32OLE
|
107
113
|
workbook = file_or_workbook
|
108
114
|
@workbook = workbook
|
109
115
|
# use the Excel instance where the workbook is opened
|
110
116
|
win32ole_excel = WIN32OLE.connect(workbook.Fullname).Application rescue nil
|
111
|
-
|
112
|
-
|
117
|
+
@excel = excel_class.new(win32ole_excel)
|
118
|
+
self.apply_options(options)
|
113
119
|
# if the Excel could not be lifted up, then create it
|
114
|
-
ensure_excel(options)
|
115
|
-
t "@excel: #{@excel}"
|
120
|
+
ensure_excel(options)
|
116
121
|
else
|
117
122
|
file = file_or_workbook
|
118
123
|
ensure_excel(options)
|
@@ -128,7 +133,35 @@ module RobustExcelOle
|
|
128
133
|
end
|
129
134
|
end
|
130
135
|
|
136
|
+
def apply_options(options) # :nodoc: #
|
137
|
+
@excel.visible = options[:visible] unless options[:visible].nil?
|
138
|
+
@excel.displayalerts = options[:displayalerts] unless options[:displayalerts].nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
# returns an Excel object when given Excel, Book or Win32ole object representing a Workbook or an Excel
|
144
|
+
def self.excel_of(object)
|
145
|
+
if object.is_a? WIN32OLE
|
146
|
+
case object.ole_obj_help.name
|
147
|
+
when /Workbook/i
|
148
|
+
new(object).excel
|
149
|
+
when /Application/i
|
150
|
+
excel_class.new(object)
|
151
|
+
else
|
152
|
+
object.excel
|
153
|
+
end
|
154
|
+
else
|
155
|
+
object.excel
|
156
|
+
end
|
157
|
+
#rescue
|
158
|
+
# t "no Excel, Book, or WIN32OLE object representing a Workbook or an Excel instance"
|
159
|
+
end
|
160
|
+
|
161
|
+
public
|
162
|
+
|
131
163
|
def ensure_excel(options)
|
164
|
+
return if @excel && @excel.alive?
|
132
165
|
if options[:excel] == :reuse
|
133
166
|
@excel = excel_class.new(:reuse => true)
|
134
167
|
end
|
@@ -146,7 +179,7 @@ module RobustExcelOle
|
|
146
179
|
excel_options[:reuse] = false
|
147
180
|
@excel = excel_class.new(excel_options)
|
148
181
|
else
|
149
|
-
@excel = options[:excel]
|
182
|
+
@excel = self.class.excel_of(options[:excel])
|
150
183
|
end
|
151
184
|
end
|
152
185
|
# if :excel => :new or (:excel => :reuse but could not reuse)
|
@@ -251,7 +284,13 @@ module RobustExcelOle
|
|
251
284
|
t "WeakRefError: #{msg.message}"
|
252
285
|
raise ExcelErrorOpen, "#{msg.message}"
|
253
286
|
end
|
287
|
+
# workaround for linked workbooks for Excel 2007:
|
288
|
+
# opening and closing a dummy workbook if Excel has no workbooks.
|
289
|
+
# delay: with visible: 0.2 sec, without visible almost none
|
290
|
+
count = workbooks.Count
|
291
|
+
workbooks.Add if @excel.Version == "12.0" && count == 0
|
254
292
|
workbooks.Open(filename,{ 'ReadOnly' => options[:read_only] })
|
293
|
+
workbooks.Item(1).Close if @excel.Version == "12.0" && count == 0
|
255
294
|
rescue WIN32OLERuntimeError => msg
|
256
295
|
t "WIN32OLERuntimeError: #{msg.message}"
|
257
296
|
if msg.message =~ /800A03EC/
|
@@ -279,6 +318,7 @@ module RobustExcelOle
|
|
279
318
|
# :raise (default) -> raises an exception
|
280
319
|
# :save -> saves the workbook before it is closed
|
281
320
|
# :forget -> closes the workbook
|
321
|
+
# :keep_open -> keep the workbook open
|
282
322
|
# :alert -> gives control to excel
|
283
323
|
def close(opts = {:if_unsaved => :raise})
|
284
324
|
if (alive? && (not @workbook.Saved) && writable) then
|
@@ -290,6 +330,8 @@ module RobustExcelOle
|
|
290
330
|
close_workbook
|
291
331
|
when :forget
|
292
332
|
close_workbook
|
333
|
+
when :keep_open
|
334
|
+
# nothing
|
293
335
|
when :alert
|
294
336
|
@excel.with_displayalerts true do
|
295
337
|
close_workbook
|
@@ -440,7 +482,9 @@ module RobustExcelOle
|
|
440
482
|
value
|
441
483
|
end
|
442
484
|
|
443
|
-
#
|
485
|
+
# sets the contents of a range with given name
|
486
|
+
# @param [String] name the range name
|
487
|
+
# @param [Variant] value the contents of the range
|
444
488
|
def set_nvalue(name, value)
|
445
489
|
begin
|
446
490
|
item = self.Names.Item(name)
|
@@ -514,7 +558,7 @@ module RobustExcelOle
|
|
514
558
|
end
|
515
559
|
|
516
560
|
# simple save of a workbook.
|
517
|
-
#
|
561
|
+
# @return [Boolean] true, if successfully saved, nil or error otherwise
|
518
562
|
def save
|
519
563
|
raise ExcelErrorSave, "Workbook is not alive" if (not alive?)
|
520
564
|
raise ExcelErrorSave, "Not opened for writing (opened with :read_only option)" if @workbook.ReadOnly
|
@@ -531,6 +575,8 @@ module RobustExcelOle
|
|
531
575
|
end
|
532
576
|
|
533
577
|
# saves a workbook with a given file name.
|
578
|
+
# @param [String] file the file name
|
579
|
+
# @option opts [Symbol] :if_exists if a file with the same name exists, then
|
534
580
|
#
|
535
581
|
# options:
|
536
582
|
# :if_exists if a file with the same name exists, then
|
@@ -630,7 +676,7 @@ module RobustExcelOle
|
|
630
676
|
|
631
677
|
public
|
632
678
|
|
633
|
-
# returns a sheet, if a name
|
679
|
+
# returns a sheet, if a sheet name or a number is given
|
634
680
|
# returns the value of the range, if a global name of a range in the book is given
|
635
681
|
def [] name
|
636
682
|
name += 1 if name.is_a? Numeric
|
@@ -646,6 +692,8 @@ module RobustExcelOle
|
|
646
692
|
end
|
647
693
|
|
648
694
|
# sets the value of a range given its name
|
695
|
+
# @param [String] name the name of the range
|
696
|
+
# @param [Variant] value the contents of the range
|
649
697
|
def []= (name, value)
|
650
698
|
set_nvalue(name,value)
|
651
699
|
end
|
@@ -656,6 +704,11 @@ module RobustExcelOle
|
|
656
704
|
end
|
657
705
|
end
|
658
706
|
|
707
|
+
# adds a sheet to the workbook
|
708
|
+
# @param [Sheet] sheet a sheet
|
709
|
+
# @option opts [Symbol] :as new name of the copyed sheet
|
710
|
+
# @option opts [Symbol] :before a sheet before which the sheet shall be inserted
|
711
|
+
# @option opts [Symbol] :after a sheet after which the sheet shall be inserted
|
659
712
|
def add_sheet(sheet = nil, opts = { })
|
660
713
|
if sheet.is_a? Hash
|
661
714
|
opts = sheet
|
@@ -700,6 +753,10 @@ module RobustExcelOle
|
|
700
753
|
"<#Book: " + "#{"not alive " unless alive?}" + "#{File.basename(self.filename) if alive?}" + " #{@workbook} #{@excel}" + ">"
|
701
754
|
end
|
702
755
|
|
756
|
+
def self.in_context(klass)
|
757
|
+
|
758
|
+
end
|
759
|
+
|
703
760
|
def self.excel_class
|
704
761
|
@excel_class ||= begin
|
705
762
|
module_name = self.parent_name
|
@@ -749,6 +806,8 @@ module RobustExcelOle
|
|
749
806
|
|
750
807
|
public
|
751
808
|
|
809
|
+
Workbook = Book
|
810
|
+
|
752
811
|
class ExcelError < RuntimeError # :nodoc: #
|
753
812
|
end
|
754
813
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
|
+
require 'timeout'
|
4
|
+
|
3
5
|
module RobustExcelOle
|
4
6
|
|
5
7
|
class Excel
|
@@ -18,18 +20,20 @@ module RobustExcelOle
|
|
18
20
|
end
|
19
21
|
|
20
22
|
# returns an Excel instance
|
21
|
-
# options:
|
22
|
-
# :reuse
|
23
|
-
#
|
23
|
+
# given: a WIN32OLE object representing an Excel instance, or a Hash representing options:
|
24
|
+
# :reuse connects to an already running Excel instance (true) or
|
25
|
+
# creates a new Excel instance (false) (default: true)
|
24
26
|
# :displayalerts allows display alerts in Excel (default: false)
|
25
27
|
# :visible makes the Excel visible (default: false)
|
26
28
|
# if :reuse => true, then DisplayAlerts and Visible are set only if they are given
|
27
|
-
def self.new(options= {})
|
28
|
-
options
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
def self.new(options = {})
|
30
|
+
if options.is_a? WIN32OLE
|
31
|
+
excel = options
|
32
|
+
else
|
33
|
+
options = {:reuse => true}.merge(options)
|
34
|
+
if options[:reuse] == true then
|
35
|
+
excel = current_excel
|
36
|
+
end
|
33
37
|
end
|
34
38
|
if not (excel)
|
35
39
|
excel = WIN32OLE.new('Excel.Application')
|
@@ -38,8 +42,10 @@ module RobustExcelOle
|
|
38
42
|
:visible => false,
|
39
43
|
}.merge(options)
|
40
44
|
end
|
41
|
-
|
42
|
-
|
45
|
+
unless options.is_a? WIN32OLE
|
46
|
+
excel.DisplayAlerts = options[:displayalerts] unless options[:displayalerts].nil?
|
47
|
+
excel.Visible = options[:visible] unless options[:visible].nil?
|
48
|
+
end
|
43
49
|
|
44
50
|
hwnd = excel.HWnd
|
45
51
|
stored = hwnd2excel(hwnd)
|
@@ -51,7 +57,6 @@ module RobustExcelOle
|
|
51
57
|
result.instance_variable_set(:@ole_excel, excel)
|
52
58
|
WIN32OLE.const_load(excel, RobustExcelOle) unless RobustExcelOle.const_defined?(:CONSTANTS)
|
53
59
|
@@hwnd2excel[hwnd] = WeakRef.new(result)
|
54
|
-
|
55
60
|
end
|
56
61
|
result
|
57
62
|
end
|
@@ -61,21 +66,24 @@ module RobustExcelOle
|
|
61
66
|
end
|
62
67
|
|
63
68
|
# reopens a closed Excel instance
|
64
|
-
# options:
|
69
|
+
# options: reopen_workbooks (default: false): reopen the workbooks in the Excel instances
|
70
|
+
# :visible (default: false), :displayalerts (default: false)
|
65
71
|
def recreate(opts = {})
|
66
72
|
unless self.alive?
|
67
73
|
opts = {
|
68
|
-
:displayalerts => false,
|
69
|
-
:visible => false
|
74
|
+
:displayalerts => @displayalerts ? @displayalerts : false,
|
75
|
+
:visible => @visible ? @visible : false
|
70
76
|
}.merge(opts)
|
71
77
|
new_excel = WIN32OLE.new('Excel.Application')
|
72
78
|
new_excel.DisplayAlerts = opts[:displayalerts]
|
73
79
|
new_excel.Visible = opts[:visible]
|
74
80
|
@ole_excel = new_excel
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
81
|
+
if opts[:reopen_workbooks]
|
82
|
+
books = book_class.books
|
83
|
+
books.each do |book|
|
84
|
+
book.reopen if ((not book.alive?) && book.excel.alive? && book.excel == self)
|
85
|
+
end
|
86
|
+
end
|
79
87
|
end
|
80
88
|
self
|
81
89
|
end
|
@@ -84,7 +92,9 @@ module RobustExcelOle
|
|
84
92
|
|
85
93
|
# returns an Excel instance to which one 'connect' was possible
|
86
94
|
def self.current_excel # :nodoc: #
|
95
|
+
#p "current_excel:"
|
87
96
|
result = WIN32OLE.connect('Excel.Application') rescue nil
|
97
|
+
#p "result: #{result}"
|
88
98
|
if result
|
89
99
|
begin
|
90
100
|
result.Visible # send any method, just to see if it responds
|
@@ -93,6 +103,7 @@ module RobustExcelOle
|
|
93
103
|
return nil
|
94
104
|
end
|
95
105
|
end
|
106
|
+
#p "result: #{result}"
|
96
107
|
result
|
97
108
|
end
|
98
109
|
|
@@ -104,24 +115,36 @@ module RobustExcelOle
|
|
104
115
|
# :raise (default) -> raises an exception
|
105
116
|
# :save -> saves the workbooks before closing
|
106
117
|
# :forget -> closes the excel instance without saving the workbooks
|
107
|
-
# :alert ->
|
108
|
-
# :hard
|
118
|
+
# :alert -> give control to Excel
|
119
|
+
# :hard closes Excel instances soft (default: false), or, additionally kills the Excel processes hard (true)
|
120
|
+
# :kill_if_timeout: kills Excel instances hard if the closing process exceeds a certain time limit (default: true)
|
109
121
|
def self.close_all(options={})
|
110
122
|
options = {
|
111
123
|
:if_unsaved => :raise,
|
112
|
-
:hard => false
|
124
|
+
:hard => false,
|
125
|
+
:kill_if_timeout => false
|
113
126
|
}.merge(options)
|
114
127
|
excels_number = excel_processes.size
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
128
|
+
timeout = false
|
129
|
+
begin
|
130
|
+
status = Timeout::timeout(5) {
|
131
|
+
while current_excel do
|
132
|
+
close_one_excel(options)
|
133
|
+
GC.start
|
134
|
+
sleep 0.3
|
135
|
+
current_excels_number = excel_processes.size
|
136
|
+
if current_excels_number == excels_number && excels_number > 0
|
137
|
+
raise ExcelError, "some Excel instance cannot be closed"
|
138
|
+
end
|
139
|
+
excels_number = current_excels_number
|
140
|
+
end
|
141
|
+
}
|
142
|
+
rescue Timeout::Error
|
143
|
+
raise ExcelError, "close_all: timeout" unless options[:kill_if_timeout]
|
144
|
+
timeout = true
|
122
145
|
end
|
146
|
+
kill_all if options[:hard] || (timeout && options[:kill_if_timeout])
|
123
147
|
init
|
124
|
-
kill_all if options[:hard]
|
125
148
|
end
|
126
149
|
|
127
150
|
def self.init
|
@@ -131,7 +154,6 @@ module RobustExcelOle
|
|
131
154
|
private
|
132
155
|
|
133
156
|
def self.manage_unsaved_workbooks(excel, options)
|
134
|
-
#this_excel = excel == 0 ? self : excel
|
135
157
|
unsaved_workbooks = []
|
136
158
|
begin
|
137
159
|
excel.Workbooks.each {|w| unsaved_workbooks << w unless (w.Saved || w.ReadOnly)}
|
@@ -149,22 +171,32 @@ module RobustExcelOle
|
|
149
171
|
end
|
150
172
|
when :forget
|
151
173
|
# nothing
|
174
|
+
when :keep_open
|
175
|
+
return
|
152
176
|
when :alert
|
153
|
-
|
177
|
+
begin
|
178
|
+
excel.DisplayAlerts = true
|
179
|
+
yield
|
180
|
+
ensure
|
181
|
+
excel.DisplayAlerts = false
|
182
|
+
end
|
183
|
+
return
|
154
184
|
else
|
155
185
|
raise ExcelErrorClose, ":if_unsaved: invalid option: #{options[:if_unsaved].inspect}"
|
156
186
|
end
|
157
187
|
end
|
188
|
+
yield
|
158
189
|
end
|
159
190
|
|
160
191
|
# closes one Excel instance to which one was connected
|
161
192
|
def self.close_one_excel(options={})
|
162
193
|
excel = current_excel
|
163
194
|
return unless excel
|
164
|
-
manage_unsaved_workbooks(excel, options)
|
165
|
-
|
166
|
-
|
167
|
-
|
195
|
+
manage_unsaved_workbooks(excel, options) do
|
196
|
+
weak_ole_excel = WeakRef.new(excel)
|
197
|
+
excel = nil
|
198
|
+
close_excel_ole_instance(weak_ole_excel.__getobj__)
|
199
|
+
end
|
168
200
|
end
|
169
201
|
|
170
202
|
def self.close_excel_ole_instance(ole_excel)
|
@@ -180,7 +212,6 @@ module RobustExcelOle
|
|
180
212
|
GC.start
|
181
213
|
sleep 0.2
|
182
214
|
if weak_excel_ref.weakref_alive? then
|
183
|
-
#if WIN32OLE.ole_reference_count(weak_xlapp) > 0
|
184
215
|
begin
|
185
216
|
weak_excel_ref.ole_free
|
186
217
|
t "successfully ole_freed #{weak_excel_ref}"
|
@@ -188,7 +219,6 @@ module RobustExcelOle
|
|
188
219
|
t "could not do ole_free on #{weak_excel_ref}"
|
189
220
|
end
|
190
221
|
end
|
191
|
-
hwnd2excel(excel_hwnd).die rescue nil
|
192
222
|
@@hwnd2excel.delete(excel_hwnd)
|
193
223
|
rescue => e
|
194
224
|
t "Error when closing Excel: #{e.message}"
|
@@ -217,11 +247,6 @@ module RobustExcelOle
|
|
217
247
|
t "went through #{anz_objekte} OLE objects"
|
218
248
|
end
|
219
249
|
|
220
|
-
# sets this Excel instance to nil
|
221
|
-
def die
|
222
|
-
@ole_excel = nil
|
223
|
-
end
|
224
|
-
|
225
250
|
public
|
226
251
|
|
227
252
|
# closes the Excel
|
@@ -229,15 +254,16 @@ module RobustExcelOle
|
|
229
254
|
# :raise (default) -> raises an exception
|
230
255
|
# :save -> saves the workbooks before closing
|
231
256
|
# :forget -> closes the Excel instance without saving the workbooks
|
232
|
-
# :
|
257
|
+
# :keep_open -> keeps the Excel instance open
|
233
258
|
# :hard kill the Excel instance hard (default: false)
|
234
259
|
def close(options = {})
|
235
260
|
options = {
|
236
261
|
:if_unsaved => :raise,
|
237
262
|
:hard => false
|
238
263
|
}.merge(options)
|
239
|
-
self.class.manage_unsaved_workbooks(@ole_excel, options)
|
240
|
-
|
264
|
+
self.class.manage_unsaved_workbooks(@ole_excel, options) do
|
265
|
+
close_excel(options)
|
266
|
+
end
|
241
267
|
end
|
242
268
|
|
243
269
|
private
|
@@ -257,7 +283,6 @@ module RobustExcelOle
|
|
257
283
|
GC.start
|
258
284
|
sleep 0.2
|
259
285
|
if weak_excel_ref.weakref_alive? then
|
260
|
-
#if WIN32OLE.ole_reference_count(weak_xlapp) > 0
|
261
286
|
begin
|
262
287
|
weak_excel_ref.ole_free
|
263
288
|
t "successfully ole_freed #{weak_excel_ref}"
|
@@ -266,7 +291,6 @@ module RobustExcelOle
|
|
266
291
|
t "could not do ole_free on #{weak_excel_ref}"
|
267
292
|
end
|
268
293
|
end
|
269
|
-
hwnd2excel(excel_hwnd).die rescue nil
|
270
294
|
@@hwnd2excel.delete(excel_hwnd)
|
271
295
|
if options[:hard] then
|
272
296
|
Excel.free_all_ole_objects
|
@@ -288,6 +312,7 @@ module RobustExcelOle
|
|
288
312
|
procs.InstancesOf("win32_process").each do |p|
|
289
313
|
Process.kill('KILL', p.processid) if p.name == "EXCEL.EXE"
|
290
314
|
end
|
315
|
+
init
|
291
316
|
number
|
292
317
|
end
|
293
318
|
|
@@ -297,11 +322,12 @@ module RobustExcelOle
|
|
297
322
|
def self.excel_processes
|
298
323
|
pid2excel = {}
|
299
324
|
@@hwnd2excel.each do |hwnd,wr_excel|
|
325
|
+
excel = wr_excel.__getobj__
|
300
326
|
process_id = Win32API.new("user32", "GetWindowThreadProcessId", ["I","P"], "I")
|
301
327
|
pid_puffer = " " * 32
|
302
328
|
process_id.call(hwnd, pid_puffer)
|
303
329
|
pid = pid_puffer.unpack("L")[0]
|
304
|
-
pid2excel[pid] =
|
330
|
+
pid2excel[pid] = excel
|
305
331
|
end
|
306
332
|
procs = WIN32OLE.connect("winmgmts:\\\\.")
|
307
333
|
processes = procs.InstancesOf("win32_process")
|
@@ -309,11 +335,11 @@ module RobustExcelOle
|
|
309
335
|
processes.each do |p|
|
310
336
|
if p.name == "EXCEL.EXE"
|
311
337
|
if pid2excel.include?(p.processid)
|
312
|
-
excel = pid2excel[p.processid]
|
338
|
+
excel = pid2excel[p.processid]
|
313
339
|
result << excel
|
314
340
|
end
|
315
|
-
# how to connect
|
316
|
-
#
|
341
|
+
# how to connect to an (interactively opened) Excel instance and get a WIN32OLE object?
|
342
|
+
# after that, lift it to an Excel object
|
317
343
|
end
|
318
344
|
end
|
319
345
|
result
|
@@ -353,7 +379,7 @@ module RobustExcelOle
|
|
353
379
|
|
354
380
|
# returns true, if the Excel instances are alive and identical, false otherwise
|
355
381
|
def == other_excel
|
356
|
-
self.Hwnd == other_excel.Hwnd
|
382
|
+
self.Hwnd == other_excel.Hwnd if other_excel.is_a?(Excel) && self.alive? && other_excel.alive?
|
357
383
|
end
|
358
384
|
|
359
385
|
# returns true, if the Excel instances responds to VBA methods, false otherwise
|
@@ -365,11 +391,18 @@ module RobustExcelOle
|
|
365
391
|
false
|
366
392
|
end
|
367
393
|
|
368
|
-
|
369
|
-
|
394
|
+
|
395
|
+
# returns all unsaved workbooks in Excel instances
|
396
|
+
def self.unsaved_workbooks_all
|
397
|
+
result = []
|
398
|
+
@@hwnd2excel.each do |hwnd,wr_excel|
|
399
|
+
excel = wr_excel.__getobj__
|
400
|
+
result << excel.unsaved_workbooks
|
401
|
+
end
|
402
|
+
result
|
370
403
|
end
|
371
404
|
|
372
|
-
# returns
|
405
|
+
# returns unsaved workbooks
|
373
406
|
def unsaved_workbooks
|
374
407
|
result = []
|
375
408
|
begin
|
@@ -383,6 +416,11 @@ module RobustExcelOle
|
|
383
416
|
# yields different WIN32OLE objects than book.workbook
|
384
417
|
#self.class.map {|w| (not w.Saved)}
|
385
418
|
|
419
|
+
def print_workbooks
|
420
|
+
self.Workbooks.each {|w| t "#{w.Name} #{w}"}
|
421
|
+
end
|
422
|
+
|
423
|
+
|
386
424
|
# empty workbook is generated, saved and closed
|
387
425
|
def generate_workbook file_name
|
388
426
|
self.Workbooks.Add
|
@@ -416,22 +454,22 @@ module RobustExcelOle
|
|
416
454
|
|
417
455
|
# enables DisplayAlerts in the current Excel instance
|
418
456
|
def displayalerts= displayalerts_value
|
419
|
-
@ole_excel.DisplayAlerts = displayalerts_value
|
457
|
+
@displayalerts = @ole_excel.DisplayAlerts = displayalerts_value
|
420
458
|
end
|
421
459
|
|
422
460
|
# return if in the current Excel instance DisplayAlerts is enabled
|
423
461
|
def displayalerts
|
424
|
-
@ole_excel.DisplayAlerts
|
462
|
+
@displayalerts = @ole_excel.DisplayAlerts
|
425
463
|
end
|
426
464
|
|
427
465
|
# makes the current Excel instance visible or invisible
|
428
466
|
def visible= visible_value
|
429
|
-
@ole_excel.Visible = visible_value
|
467
|
+
@visible = @ole_excel.Visible = visible_value
|
430
468
|
end
|
431
469
|
|
432
470
|
# returns whether the current Excel instance is visible
|
433
471
|
def visible
|
434
|
-
@ole_excel.Visible
|
472
|
+
@visible = @ole_excel.Visible
|
435
473
|
end
|
436
474
|
|
437
475
|
def to_s
|
@@ -442,6 +480,19 @@ module RobustExcelOle
|
|
442
480
|
self.to_s
|
443
481
|
end
|
444
482
|
|
483
|
+
def self.book_class
|
484
|
+
@book_class ||= begin
|
485
|
+
module_name = self.parent_name
|
486
|
+
"#{module_name}::Book".constantize
|
487
|
+
rescue NameError => e
|
488
|
+
book
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def book_class
|
493
|
+
self.class.book_class
|
494
|
+
end
|
495
|
+
|
445
496
|
private
|
446
497
|
|
447
498
|
def method_missing(name, *args)
|