robust_excel_ole 0.3.6 → 0.3.7
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 +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)
|