robust_excel_ole 1.31 → 1.32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog +20 -1
- data/README.rdoc +118 -18
- data/___dummy_workbook.xls +0 -0
- data/benchmarking/creek_example.rb +1 -1
- data/benchmarking/roo_example.rb +1 -1
- data/benchmarking/simple_xlsx_reader_example.rb +1 -1
- data/benchmarking/spreadsheet_example.rb +1 -1
- data/docs/README_excel.rdoc +16 -24
- data/docs/README_listobjects.rdoc +176 -0
- data/docs/README_open.rdoc +12 -12
- data/docs/README_ranges.rdoc +72 -55
- data/docs/README_save_close.rdoc +3 -3
- data/docs/README_sheet.rdoc +18 -13
- data/examples/example_ruby_library.rb +2 -2
- data/examples/introductory_examples/example_range.rb +2 -2
- data/examples/modifying_sheets/example_access_sheets_and_cells.rb +6 -6
- data/examples/modifying_sheets/example_add_names.rb +1 -1
- data/examples/modifying_sheets/example_concating.rb +1 -1
- data/examples/modifying_sheets/example_copying.rb +2 -2
- data/examples/modifying_sheets/example_listobjects.rb +86 -0
- data/examples/modifying_sheets/example_naming.rb +1 -1
- data/examples/modifying_sheets/example_ranges.rb +1 -1
- data/examples/open_save_close/example_control_to_excel.rb +1 -1
- data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
- data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
- data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
- data/examples/open_save_close/example_if_unsaved_forget.rb +3 -3
- data/examples/open_save_close/example_if_unsaved_forget_more.rb +4 -4
- data/examples/open_save_close/example_read_only.rb +1 -1
- data/examples/open_save_close/example_simple.rb +1 -1
- data/examples/open_save_close/example_unobtrusively.rb +3 -3
- data/lib/robust_excel_ole/address_tool.rb +54 -44
- data/lib/robust_excel_ole/base.rb +4 -6
- data/lib/robust_excel_ole/bookstore.rb +2 -16
- data/lib/robust_excel_ole/cell.rb +16 -21
- data/lib/robust_excel_ole/excel.rb +131 -186
- data/lib/robust_excel_ole/general.rb +82 -55
- data/lib/robust_excel_ole/list_object.rb +182 -109
- data/lib/robust_excel_ole/list_row.rb +65 -38
- data/lib/robust_excel_ole/range.rb +125 -93
- data/lib/robust_excel_ole/range_owners.rb +52 -66
- data/lib/robust_excel_ole/version.rb +1 -1
- data/lib/robust_excel_ole/workbook.rb +168 -176
- data/lib/robust_excel_ole/worksheet.rb +177 -141
- data/robust_excel_ole.gemspec +4 -3
- data/spec/bookstore_spec.rb +2 -3
- data/spec/cell_spec.rb +9 -9
- data/spec/data/more_data/workbook.xls +0 -0
- data/spec/excel_spec.rb +132 -85
- data/spec/general_spec.rb +47 -15
- data/spec/list_object_spec.rb +258 -145
- data/spec/list_row_spec.rb +218 -0
- data/spec/range_spec.rb +76 -29
- data/spec/spec_helper.rb +15 -1
- data/spec/workbook_spec.rb +75 -34
- data/spec/workbook_specs/workbook_all_spec.rb +2 -1
- data/spec/workbook_specs/workbook_misc_spec.rb +20 -13
- data/spec/workbook_specs/workbook_open_spec.rb +47 -45
- data/spec/workbook_specs/workbook_save_spec.rb +21 -22
- data/spec/workbook_specs/workbook_sheet_spec.rb +3 -3
- data/spec/workbook_specs/workbook_unobtr_spec.rb +303 -303
- data/spec/worksheet_spec.rb +522 -318
- metadata +37 -2
@@ -16,71 +16,80 @@ module RobustExcelOle
|
|
16
16
|
# integer_ranges-fromat: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
|
17
17
|
# [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
|
18
18
|
# a1-format: e.g. "A3", "A3:B5", "A:B", "3:5", "A", "3"
|
19
|
-
|
20
19
|
def as_r1c1(address)
|
21
|
-
transform_address(address
|
20
|
+
transform_address(address, :r1c1)
|
22
21
|
end
|
23
22
|
|
24
23
|
def as_a1(address)
|
25
|
-
transform_address(address
|
24
|
+
transform_address(address, :a1)
|
26
25
|
end
|
27
26
|
|
28
27
|
# valid address formats: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
|
29
28
|
# [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
|
30
29
|
def as_integer_ranges(address)
|
31
|
-
transform_address(address
|
30
|
+
transform_address(address, :int_range)
|
32
31
|
end
|
33
|
-
|
32
|
+
|
34
33
|
private
|
35
34
|
|
36
35
|
def transform_address(address, format)
|
37
36
|
address = address.is_a?(Array) ? address : [address]
|
38
37
|
raise AddressInvalid, "address #{address.inspect} has more than two components" if address.size > 2
|
39
|
-
begin
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
is_r1c1 = comp1 =~ r1c1_expr && (comp2.nil? || comp2 =~ r1c1_expr) && (not is_a1)
|
46
|
-
raise AddressInvalid, "address #{address.inspect} not in A1- or r1c1-format" unless (is_a1 || is_r1c1)
|
47
|
-
return address[0].gsub('[','(').gsub(']',')') if (is_a1 && format==:a1) || (is_r1c1 && format==:r1c1)
|
48
|
-
given_format = (is_a1) ? :a1 : :r1c1
|
49
|
-
row_comp1, col_comp1 = analyze(comp1,given_format)
|
50
|
-
row_comp2, col_comp2 = analyze(comp2,given_format) unless comp2.nil?
|
51
|
-
address_comp1 = comp2 && (not row_comp1.nil?) ? (row_comp1 .. row_comp2) : row_comp1
|
52
|
-
address_comp2 = comp2 && (not col_comp1.nil?) ? (col_comp1 .. col_comp2) : col_comp1
|
53
|
-
else
|
54
|
-
address_comp1, address_comp2 = address
|
55
|
-
end
|
56
|
-
address_comp1 = address_comp1..address_comp1 if (address_comp1.is_a?(Integer) || address_comp1.is_a?(String) || address_comp1.is_a?(Array))
|
57
|
-
address_comp2 = address_comp2..address_comp2 if (address_comp2.is_a?(Integer) || address_comp2.is_a?(String) || address_comp2.is_a?(Array))
|
58
|
-
#raise unless address_comp1.nil? || address_comp1.begin.to_i!=0 || address_comp1.begin.empty?
|
59
|
-
rows = unless address_comp1.nil? || address_comp1.begin == 0 # || address_comp1.begin.to_i==0
|
60
|
-
address_comp1.begin..address_comp1.end
|
61
|
-
end
|
62
|
-
columns = unless address_comp2.nil?
|
63
|
-
if address_comp2.begin.is_a?(String) #address_comp2.begin.to_i == 0
|
64
|
-
col_range = str2num(address_comp2.begin)..str2num(address_comp2.end)
|
65
|
-
col_range==(0..0) ? nil : col_range
|
66
|
-
else
|
67
|
-
address_comp2.begin..address_comp2.end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
rescue
|
71
|
-
raise AddressInvalid, "address (#{address.inspect}) format not correct"
|
38
|
+
rows, columns = begin
|
39
|
+
rows_and_columns(address, format)
|
40
|
+
rescue AddressInvalid
|
41
|
+
raise
|
42
|
+
rescue AddressAlreadyInRightFormat
|
43
|
+
return address[0].gsub('[','(').gsub(']',')')
|
72
44
|
end
|
73
|
-
if format
|
45
|
+
if format == :int_range
|
46
|
+
[rows,columns]
|
47
|
+
elsif format == :r1c1
|
74
48
|
r1c1_string(@row_letter,rows,:min) + r1c1_string(@col_letter,columns,:min) + ":" +
|
75
49
|
r1c1_string(@row_letter,rows,:max) + r1c1_string(@col_letter,columns,:max)
|
76
|
-
elsif format==:int_range
|
77
|
-
[rows,columns]
|
78
50
|
else
|
79
51
|
raise NotImplementedREOError, "not implemented"
|
80
52
|
end
|
81
|
-
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def rows_and_columns(address, format)
|
56
|
+
if address.size != 1
|
57
|
+
address_comp1, address_comp2 = address
|
58
|
+
else
|
59
|
+
comp1, comp2 = address[0].split(':')
|
60
|
+
a1_expr = /^(([A-Z]+[0-9]+)|([A-Z]+$)|([0-9]+))$/
|
61
|
+
is_a1 = comp1 =~ a1_expr && (comp2.nil? || comp2 =~ a1_expr)
|
62
|
+
r1c1_expr = /^(([A-Z]\[?-?[0-9]+\]?[A-Z]\[?-?[0-9]+\]?)|([A-Z]\[?-?[0-9]+\]?)|([A-Z]\[?-?[0-9]+\]?))$/
|
63
|
+
is_r1c1 = comp1 =~ r1c1_expr && (comp2.nil? || comp2 =~ r1c1_expr) && (not is_a1)
|
64
|
+
raise AddressInvalid, "address #{address.inspect} not in A1- or r1c1-format" unless (is_a1 || is_r1c1)
|
65
|
+
raise AddressAlreadyInRightFormat if (is_a1 && format==:a1) || (is_r1c1 && format==:r1c1)
|
66
|
+
given_format = (is_a1) ? :a1 : :r1c1
|
67
|
+
row_comp1, col_comp1 = analyze(comp1,given_format)
|
68
|
+
row_comp2, col_comp2 = analyze(comp2,given_format) unless comp2.nil?
|
69
|
+
address_comp1 = comp2 && (not row_comp1.nil?) ? (row_comp1 .. row_comp2) : row_comp1
|
70
|
+
address_comp2 = comp2 && (not col_comp1.nil?) ? (col_comp1 .. col_comp2) : col_comp1
|
71
|
+
end
|
72
|
+
address_comp1 = address_comp1..address_comp1 if (address_comp1.is_a?(Integer) || address_comp1.is_a?(String) || address_comp1.is_a?(Array))
|
73
|
+
address_comp2 = address_comp2..address_comp2 if (address_comp2.is_a?(Integer) || address_comp2.is_a?(String) || address_comp2.is_a?(Array))
|
74
|
+
raise AddressInvalid, "address contains row 0" if !address_comp1.nil? && address_comp1.begin == 0
|
75
|
+
rows = address_comp1.begin..address_comp1.end unless address_comp1.nil? || address_comp1.begin == 0
|
76
|
+
columns = unless address_comp2.nil?
|
77
|
+
if address_comp2.begin.is_a?(String) #address_comp2.begin.to_i == 0
|
78
|
+
col_range = str2num(address_comp2.begin)..str2num(address_comp2.end)
|
79
|
+
col_range==(0..0) ? nil : col_range
|
80
|
+
else
|
81
|
+
address_comp2.begin..address_comp2.end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
[rows, columns]
|
85
|
+
rescue
|
86
|
+
raise AddressInvalid, "address (#{address.inspect}) format not correct"
|
87
|
+
end
|
82
88
|
|
83
|
-
|
89
|
+
class AddressAlreadyInRightFormat < Exception
|
90
|
+
end
|
91
|
+
|
92
|
+
def r1c1_string(letter, int_range,type)
|
84
93
|
return "" if int_range.nil? || int_range.begin.nil?
|
85
94
|
parameter = type == :min ? int_range.begin : int_range.end
|
86
95
|
is_relative = parameter.is_a?(Array)
|
@@ -88,7 +97,7 @@ module RobustExcelOle
|
|
88
97
|
letter + (is_relative ? "(" : "") + parameter.to_s + (is_relative ? ")" : "")
|
89
98
|
end
|
90
99
|
|
91
|
-
def analyze(comp,format)
|
100
|
+
def analyze(comp, format)
|
92
101
|
row_comp, col_comp = if format==:a1
|
93
102
|
[comp.gsub(/[A-Z]/,''), comp.gsub(/[0-9]/,'')]
|
94
103
|
else
|
@@ -108,6 +117,7 @@ module RobustExcelOle
|
|
108
117
|
|
109
118
|
end
|
110
119
|
|
120
|
+
|
111
121
|
# @private
|
112
122
|
class AddressInvalid < REOError
|
113
123
|
end
|
@@ -69,7 +69,7 @@ module RobustExcelOle
|
|
69
69
|
|
70
70
|
# @private
|
71
71
|
def own_methods
|
72
|
-
(
|
72
|
+
(methods - Object.methods).sort
|
73
73
|
end
|
74
74
|
|
75
75
|
# @private
|
@@ -99,12 +99,10 @@ module RobustExcelOle
|
|
99
99
|
def self.puts_hash(hash)
|
100
100
|
hash.each do |e|
|
101
101
|
if e[1].is_a?(Hash)
|
102
|
-
puts "#{e[0]}
|
103
|
-
e[1].each
|
104
|
-
puts " #{f[0]} => #{f[1]}"
|
105
|
-
end
|
102
|
+
puts "#{e[0]}: "
|
103
|
+
e[1].each{ |f| puts " #{f[0]}: #{f[1]}" }
|
106
104
|
else
|
107
|
-
puts "#{e[0]}
|
105
|
+
puts "#{e[0]}: #{e[1]}"
|
108
106
|
end
|
109
107
|
end
|
110
108
|
end
|
@@ -29,19 +29,17 @@ module RobustExcelOle
|
|
29
29
|
# otherwise returns the workbook according to the preference order mentioned above
|
30
30
|
# :prefer_excel returns the workbook in the given Excel instance, if it exists,
|
31
31
|
# otherwise proceeds according to prefer_writable
|
32
|
-
def fetch(filename, options = { :
|
32
|
+
def fetch(filename, options = { prefer_writable: true })
|
33
33
|
return nil unless filename
|
34
34
|
filename = General.absolute_path(filename)
|
35
35
|
filename_key = General.canonize(filename).downcase
|
36
36
|
weakref_books = @filename2books[filename_key]
|
37
37
|
return nil if weakref_books.nil? || weakref_books.empty?
|
38
|
-
|
39
38
|
result = open_book = closed_book = nil
|
40
39
|
weakref_books = weakref_books.map { |wr_book| wr_book if wr_book.weakref_alive? }.compact
|
41
40
|
@filename2books[filename_key] = weakref_books
|
42
41
|
weakref_books.each do |wr_book|
|
43
42
|
if !wr_book.weakref_alive?
|
44
|
-
# trace "warn: this should never happen"
|
45
43
|
begin
|
46
44
|
@filename2books[filename_key].delete(wr_book)
|
47
45
|
rescue
|
@@ -50,7 +48,6 @@ module RobustExcelOle
|
|
50
48
|
else
|
51
49
|
book = wr_book.__getobj__
|
52
50
|
next if book.excel == try_hidden_excel
|
53
|
-
|
54
51
|
if options[:prefer_excel] && book.excel == options[:prefer_excel]
|
55
52
|
result = book
|
56
53
|
break
|
@@ -64,7 +61,6 @@ module RobustExcelOle
|
|
64
61
|
end
|
65
62
|
end
|
66
63
|
result ||= (open_book || closed_book)
|
67
|
-
result
|
68
64
|
end
|
69
65
|
|
70
66
|
# stores a workbook
|
@@ -90,17 +86,7 @@ module RobustExcelOle
|
|
90
86
|
|
91
87
|
# returns all stored books
|
92
88
|
def books
|
93
|
-
|
94
|
-
if @filename2books
|
95
|
-
@filename2books.each do |_filename,books|
|
96
|
-
next if books.empty?
|
97
|
-
|
98
|
-
books.each do |wr_book|
|
99
|
-
result << wr_book.__getobj__ if wr_book.weakref_alive?
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
result
|
89
|
+
@filename2books.map{ |_fn,books| books.map{ |wr_bk| wr_bk.__getobj__ if wr_bk.weakref_alive?}.compact}.flatten
|
104
90
|
end
|
105
91
|
|
106
92
|
private
|
@@ -7,7 +7,7 @@ module RobustExcelOle
|
|
7
7
|
class Cell < Range
|
8
8
|
#attr_reader :ole_cell
|
9
9
|
|
10
|
-
def initialize(win32_cell, worksheet)
|
10
|
+
def initialize(win32_cell, worksheet)
|
11
11
|
super
|
12
12
|
ole_cell
|
13
13
|
end
|
@@ -20,8 +20,8 @@ module RobustExcelOle
|
|
20
20
|
self.Value = value
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
alias v value
|
24
|
+
alias v= value=
|
25
25
|
|
26
26
|
# @private
|
27
27
|
def ole_cell
|
@@ -30,36 +30,31 @@ module RobustExcelOle
|
|
30
30
|
|
31
31
|
# @private
|
32
32
|
def to_s
|
33
|
-
"#<Cell:
|
33
|
+
"#<Cell: (#{@ole_range.Row},#{@ole_range.Column})>"
|
34
34
|
end
|
35
35
|
|
36
36
|
# @private
|
37
37
|
def inspect
|
38
|
-
|
38
|
+
to_s[0..-2] + " #{@ole_range.Parent.Name}>"
|
39
39
|
end
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
43
|
# @private
|
44
44
|
def method_missing(name, *args)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
52
|
-
end
|
53
|
-
else
|
54
|
-
begin
|
55
|
-
#@ole_cell.send(name, *args)
|
56
|
-
@ole_range.send(name, *args)
|
57
|
-
rescue NoMethodError
|
58
|
-
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
59
|
-
end
|
45
|
+
super unless name.to_s[0,1] =~ /[A-Z]/
|
46
|
+
if ::ERRORMESSAGE_JRUBY_BUG
|
47
|
+
begin
|
48
|
+
@ole_range.send(name, *args)
|
49
|
+
rescue Java::OrgRacobCom::ComFailException
|
50
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
60
51
|
end
|
61
52
|
else
|
62
|
-
|
53
|
+
begin
|
54
|
+
@ole_range.send(name, *args)
|
55
|
+
rescue NoMethodError
|
56
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
57
|
+
end
|
63
58
|
end
|
64
59
|
end
|
65
60
|
end
|
@@ -16,6 +16,8 @@ module RobustExcelOle
|
|
16
16
|
|
17
17
|
class Excel < VbaObjects
|
18
18
|
|
19
|
+
include Enumerable
|
20
|
+
|
19
21
|
attr_reader :ole_excel
|
20
22
|
attr_reader :properties
|
21
23
|
attr_reader :address_tool
|
@@ -34,7 +36,7 @@ module RobustExcelOle
|
|
34
36
|
# @option options [Boolean] :screenupdating
|
35
37
|
# @return [Excel] a new Excel instance
|
36
38
|
def self.create(options = {})
|
37
|
-
new(options.merge(:
|
39
|
+
new(options.merge(reuse: false))
|
38
40
|
end
|
39
41
|
|
40
42
|
# connects to the current (first opened) Excel instance, if such a running Excel instance exists
|
@@ -45,7 +47,7 @@ module RobustExcelOle
|
|
45
47
|
# @option options [Boolean] :screenupdating
|
46
48
|
# @return [Excel] an Excel instance
|
47
49
|
def self.current(options = {})
|
48
|
-
new(options.merge(:
|
50
|
+
new(options.merge(reuse: true))
|
49
51
|
end
|
50
52
|
|
51
53
|
# returns an Excel instance
|
@@ -70,10 +72,11 @@ module RobustExcelOle
|
|
70
72
|
options = win32ole_excel
|
71
73
|
win32ole_excel = nil
|
72
74
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
options = { reuse: true }.merge(options)
|
76
|
+
ole_xl = if !win32ole_excel.nil?
|
77
|
+
win32ole_excel
|
78
|
+
elsif options[:reuse] == true
|
79
|
+
current_ole_excel
|
77
80
|
end
|
78
81
|
connected = (not ole_xl.nil?) && win32ole_excel.nil?
|
79
82
|
ole_xl ||= WIN32OLE.new('Excel.Application')
|
@@ -87,14 +90,9 @@ module RobustExcelOle
|
|
87
90
|
WIN32OLE.const_load(ole_xl, RobustExcelOle) unless RobustExcelOle.const_defined?(:CONSTANTS)
|
88
91
|
@@hwnd2excel[hwnd] = WeakRef.new(result)
|
89
92
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
unless reused || connected
|
94
|
-
options = { :displayalerts => :if_visible, :visible => false, :screenupdating => true }.merge(options)
|
95
|
-
end
|
96
|
-
result.set_options(options)
|
97
|
-
end
|
93
|
+
reused = options[:reuse] && stored && stored.alive?
|
94
|
+
options = { displayalerts: :if_visible, visible: false, screenupdating: true }.merge(options) unless reused || connected
|
95
|
+
result.set_options(options)
|
98
96
|
result
|
99
97
|
end
|
100
98
|
|
@@ -111,15 +109,12 @@ module RobustExcelOle
|
|
111
109
|
# @return [Excel] an Excel instance
|
112
110
|
def recreate(opts = {})
|
113
111
|
unless alive?
|
114
|
-
opts = {:
|
115
|
-
{:
|
112
|
+
opts = {visible: false, displayalerts: :if_visible}.merge(
|
113
|
+
{visible: @properties[:visible], displayalerts: @properties[:displayalerts]}).merge(opts)
|
116
114
|
@ole_excel = WIN32OLE.new('Excel.Application')
|
117
115
|
set_options(opts)
|
118
116
|
if opts[:reopen_workbooks]
|
119
|
-
books
|
120
|
-
books.each do |book|
|
121
|
-
book.reopen if !book.alive? && book.excel.alive? && book.excel == self
|
122
|
-
end
|
117
|
+
workbook_class.books.each{ |book| book.reopen if !book.alive? && book.excel.alive? && book.excel == self }
|
123
118
|
end
|
124
119
|
end
|
125
120
|
self
|
@@ -147,34 +142,29 @@ module RobustExcelOle
|
|
147
142
|
end
|
148
143
|
|
149
144
|
def ole_workbooks
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
raise ExcelREOError, 'workbooks could not be determined'
|
157
|
-
end
|
145
|
+
@ole_excel.Workbooks
|
146
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
147
|
+
if msg.message =~ /failed to get Dispatch Interface/
|
148
|
+
raise ExcelDamaged, "Excel instance not alive or damaged"
|
149
|
+
else
|
150
|
+
raise ExcelREOError, "workbooks could not be determined\n#{$!.message}"
|
158
151
|
end
|
159
152
|
end
|
160
153
|
|
161
154
|
public
|
162
155
|
|
163
156
|
# @private
|
157
|
+
# returns unsaved workbooks (win32ole objects)
|
164
158
|
def self.contains_unsaved_workbooks?
|
165
159
|
!Excel.current.unsaved_workbooks.empty?
|
166
160
|
end
|
167
161
|
|
168
|
-
# returns unsaved workbooks (win32ole objects)
|
169
162
|
# @private
|
163
|
+
# returns unsaved workbooks (win32ole objects)
|
170
164
|
def unsaved_workbooks
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
rescue RuntimeError => msg
|
175
|
-
raise ExcelDamaged, 'Excel instance not alive or damaged' if msg.message =~ /failed to get Dispatch Interface/
|
176
|
-
end
|
177
|
-
unsaved_workbooks
|
165
|
+
@ole_excel.Workbooks.reject { |w| w.Saved || w.ReadOnly }
|
166
|
+
rescue RuntimeError => msg
|
167
|
+
raise ExcelDamaged, "Excel instance not alive or damaged" if msg.message =~ /failed to get Dispatch Interface/
|
178
168
|
end
|
179
169
|
|
180
170
|
# closes workbooks
|
@@ -185,7 +175,7 @@ module RobustExcelOle
|
|
185
175
|
# :save -> saves the workbooks before closing
|
186
176
|
# :alert -> let Excel do it
|
187
177
|
# @private
|
188
|
-
def close_workbooks(options = { :
|
178
|
+
def close_workbooks(options = { if_unsaved: :raise })
|
189
179
|
return unless alive?
|
190
180
|
|
191
181
|
weak_wkbks = @ole_excel.Workbooks
|
@@ -208,11 +198,12 @@ module RobustExcelOle
|
|
208
198
|
"\nHint: Valid values are :raise, :forget, :save and :alert"
|
209
199
|
end
|
210
200
|
end
|
201
|
+
|
211
202
|
begin
|
212
203
|
@ole_excel.Workbooks.Close
|
213
204
|
rescue
|
214
205
|
if $!.message =~ /kann nicht zugeordnet werden/ or $!.message =~ /800A03EC/
|
215
|
-
raise ExcelREOError,
|
206
|
+
raise ExcelREOError, "user canceled or runtime error"
|
216
207
|
else
|
217
208
|
raise UnexpectedREOError, "unknown WIN32OLERuntimeError: #{msg.message}"
|
218
209
|
end
|
@@ -235,7 +226,7 @@ module RobustExcelOle
|
|
235
226
|
# :forget -> closes the excel instance without saving the workbooks
|
236
227
|
# :alert -> give control to Excel
|
237
228
|
# @option options [Proc] block
|
238
|
-
def self.close_all(options = { :
|
229
|
+
def self.close_all(options = { if_unsaved: :raise }, &blk)
|
239
230
|
options[:if_unsaved] = blk if blk
|
240
231
|
finished_number = error_number = overall_number = 0
|
241
232
|
first_error = nil
|
@@ -243,10 +234,9 @@ module RobustExcelOle
|
|
243
234
|
if excel
|
244
235
|
begin
|
245
236
|
overall_number += 1
|
246
|
-
finished_number += excel.close(:
|
237
|
+
finished_number += excel.close(if_unsaved: options[:if_unsaved])
|
247
238
|
rescue
|
248
239
|
first_error = $!
|
249
|
-
#trace "error when finishing #{$!}"
|
250
240
|
error_number += 1
|
251
241
|
end
|
252
242
|
end
|
@@ -269,19 +259,13 @@ module RobustExcelOle
|
|
269
259
|
old_error_number = error_number
|
270
260
|
9.times do |_index|
|
271
261
|
sleep 0.1
|
272
|
-
excel =
|
273
|
-
new(WIN32OLE.connect('Excel.Application'))
|
274
|
-
rescue
|
275
|
-
nil
|
276
|
-
end
|
262
|
+
excel = new(WIN32OLE.connect('Excel.Application')) rescue nil
|
277
263
|
finishing_action.call(excel) if excel
|
278
264
|
free_all_ole_objects unless (error_number > 0) && (options[:if_unsaved] == :raise)
|
279
265
|
break unless excel
|
280
266
|
break if error_number > old_error_number # + 3
|
281
267
|
end
|
282
|
-
|
283
268
|
raise first_error if ((options[:if_unsaved] == :raise) && first_error) || (first_error.class == OptionInvalid)
|
284
|
-
|
285
269
|
[finished_number, error_number]
|
286
270
|
end
|
287
271
|
|
@@ -294,19 +278,13 @@ module RobustExcelOle
|
|
294
278
|
# :save -> saves the workbooks before closing
|
295
279
|
# :forget -> closes the Excel instance without saving the workbooks
|
296
280
|
# :alert -> Excel takes over
|
297
|
-
def close(options = { :
|
281
|
+
def close(options = { if_unsaved: :raise })
|
298
282
|
finishing_living_excel = alive?
|
299
283
|
if finishing_living_excel
|
300
|
-
hwnd =
|
301
|
-
|
302
|
-
rescue
|
303
|
-
nil
|
304
|
-
end)
|
305
|
-
close_workbooks(:if_unsaved => options[:if_unsaved])
|
284
|
+
hwnd = @ole_excel.Hwnd rescue nil
|
285
|
+
close_workbooks(if_unsaved: options[:if_unsaved])
|
306
286
|
@ole_excel.Quit
|
307
|
-
if false && defined?(weak_wkbks) && weak_wkbks.weakref_alive?
|
308
|
-
weak_wkbks.ole_free
|
309
|
-
end
|
287
|
+
weak_wkbks.ole_free if false && defined?(weak_wkbks) && weak_wkbks.weakref_alive?
|
310
288
|
weak_xl = WeakRef.new(@ole_excel)
|
311
289
|
else
|
312
290
|
weak_xl = nil
|
@@ -320,21 +298,10 @@ module RobustExcelOle
|
|
320
298
|
pid_puffer = ' ' * 32
|
321
299
|
process_id.call(hwnd, pid_puffer)
|
322
300
|
pid = pid_puffer.unpack('L')[0]
|
323
|
-
|
324
|
-
Process.kill('KILL', pid)
|
325
|
-
rescue
|
326
|
-
# trace "kill_error: #{$!}"
|
327
|
-
end
|
301
|
+
Process.kill('KILL', pid) rescue nil
|
328
302
|
end
|
329
303
|
@@hwnd2excel.delete(hwnd)
|
330
|
-
if weak_xl.weakref_alive?
|
331
|
-
# if WIN32OLE.ole_reference_count(weak_xlapp) > 0
|
332
|
-
begin
|
333
|
-
weak_xl.ole_free
|
334
|
-
rescue
|
335
|
-
# trace "weakref_probl_olefree"
|
336
|
-
end
|
337
|
-
end
|
304
|
+
weak_xl.ole_free if weak_xl.weakref_alive?
|
338
305
|
end
|
339
306
|
weak_xl ? 1 : 0
|
340
307
|
end
|
@@ -383,15 +350,42 @@ module RobustExcelOle
|
|
383
350
|
number
|
384
351
|
end
|
385
352
|
|
386
|
-
def self.
|
387
|
-
|
388
|
-
processes.select { |p| p.Name == 'EXCEL.EXE' }.size
|
353
|
+
def self.instance_count
|
354
|
+
WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process').select { |p| p.Name == 'EXCEL.EXE' }.size
|
389
355
|
end
|
390
356
|
|
391
|
-
def self.
|
357
|
+
def self.known_instance_count
|
392
358
|
@@hwnd2excel.size
|
393
359
|
end
|
394
360
|
|
361
|
+
# returns a running Excel instance opened with RobustExcelOle
|
362
|
+
def self.known_running_instance
|
363
|
+
self.known_running_instances.first
|
364
|
+
end
|
365
|
+
|
366
|
+
# @return [Enumerator] known running Excel instances
|
367
|
+
def self.known_running_instances
|
368
|
+
pid2excel = {}
|
369
|
+
@@hwnd2excel.each do |hwnd,wr_excel|
|
370
|
+
next unless wr_excel.weakref_alive?
|
371
|
+
excel = wr_excel.__getobj__
|
372
|
+
process_id = Win32API.new('user32', 'GetWindowThreadProcessId', %w[I P], 'I')
|
373
|
+
pid_puffer = ' ' * 32
|
374
|
+
process_id.call(hwnd, pid_puffer)
|
375
|
+
pid = pid_puffer.unpack('L')[0]
|
376
|
+
pid2excel[pid] = excel
|
377
|
+
end
|
378
|
+
processes = WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process')
|
379
|
+
processes.map{ |p| pid2excel[p.ProcessId] if p.Name == 'EXCEL.EXE'}.compact.lazy.each
|
380
|
+
end
|
381
|
+
|
382
|
+
class << self
|
383
|
+
alias excels_number instance_count # :deprecated :#
|
384
|
+
alias known_excels_number known_instance_count # :deprecated :#
|
385
|
+
alias known_excel_instance known_running_instance # :deprecated :#
|
386
|
+
alias known_excel_instances known_running_instances # :deprecated :#
|
387
|
+
end
|
388
|
+
|
395
389
|
private
|
396
390
|
|
397
391
|
# returns a Win32OLE object that represents a Excel instance to which Excel connects
|
@@ -399,7 +393,7 @@ module RobustExcelOle
|
|
399
393
|
# if this Excel instance is being closed, then Excel creates a new Excel instance
|
400
394
|
def self.current_ole_excel
|
401
395
|
if ::CONNECT_EXCEL_JRUBY_BUG
|
402
|
-
result =
|
396
|
+
result = known_running_instance
|
403
397
|
if result.nil?
|
404
398
|
if excels_number > 0
|
405
399
|
dummy_ole_workbook = WIN32OLE.connect(General.absolute_path('___dummy_workbook.xls')) rescue nil
|
@@ -428,17 +422,6 @@ module RobustExcelOle
|
|
428
422
|
result
|
429
423
|
end
|
430
424
|
|
431
|
-
# returns an Excel instance
|
432
|
-
def self.known_excel_instance
|
433
|
-
@@hwnd2excel.each do |hwnd, wr_excel|
|
434
|
-
if wr_excel.weakref_alive?
|
435
|
-
excel = wr_excel.__getobj__
|
436
|
-
return excel if excel.alive?
|
437
|
-
end
|
438
|
-
end
|
439
|
-
nil
|
440
|
-
end
|
441
|
-
|
442
425
|
def self.hwnd2excel(hwnd)
|
443
426
|
excel_weakref = @@hwnd2excel[hwnd]
|
444
427
|
if excel_weakref
|
@@ -458,35 +441,6 @@ module RobustExcelOle
|
|
458
441
|
|
459
442
|
public
|
460
443
|
|
461
|
-
# returns all Excel objects for all Excel instances opened with RobustExcelOle
|
462
|
-
def self.known_excel_instances
|
463
|
-
pid2excel = {}
|
464
|
-
@@hwnd2excel.each do |hwnd,wr_excel|
|
465
|
-
next unless wr_excel.weakref_alive?
|
466
|
-
|
467
|
-
excel = wr_excel.__getobj__
|
468
|
-
process_id = Win32API.new('user32', 'GetWindowThreadProcessId', %w[I P], 'I')
|
469
|
-
pid_puffer = ' ' * 32
|
470
|
-
process_id.call(hwnd, pid_puffer)
|
471
|
-
pid = pid_puffer.unpack('L')[0]
|
472
|
-
pid2excel[pid] = excel
|
473
|
-
end
|
474
|
-
processes = WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process')
|
475
|
-
processes.select { |p| Excel.new(pid2excel[p.ProcessId]) if p.Name == 'EXCEL.EXE' && pid2excel.include?(p.ProcessId) }
|
476
|
-
result = []
|
477
|
-
processes.each do |p|
|
478
|
-
next unless p.Name == 'EXCEL.EXE'
|
479
|
-
|
480
|
-
if pid2excel.include?(p.ProcessId)
|
481
|
-
excel = pid2excel[p.ProcessId]
|
482
|
-
result << excel
|
483
|
-
end
|
484
|
-
# how to connect to an (interactively opened) Excel instance and get a WIN32OLE object?
|
485
|
-
# after that, lift it to an Excel object
|
486
|
-
end
|
487
|
-
result
|
488
|
-
end
|
489
|
-
|
490
444
|
# @private
|
491
445
|
def excel
|
492
446
|
self
|
@@ -524,15 +478,11 @@ module RobustExcelOle
|
|
524
478
|
|
525
479
|
# returns unsaved workbooks in known (not opened by user) Excel instances
|
526
480
|
# @private
|
527
|
-
def self.unsaved_known_workbooks
|
528
|
-
|
529
|
-
@@hwnd2excel.each do |_hwnd,wr_excel|
|
530
|
-
excel = wr_excel.__getobj__ if wr_excel.weakref_alive?
|
531
|
-
result << excel.unsaved_workbooks
|
532
|
-
end
|
533
|
-
result
|
481
|
+
def self.unsaved_known_workbooks
|
482
|
+
@@hwnd2excel.values.map{ |wk_exl| wk_exl.__getobj__.unsaved_workbooks if wk_exl.weakref_alive? }.compact.flatten
|
534
483
|
end
|
535
484
|
|
485
|
+
|
536
486
|
# @private
|
537
487
|
def print_workbooks
|
538
488
|
self.Workbooks.each { |w| trace "#{w.Name} #{w}" }
|
@@ -540,7 +490,7 @@ module RobustExcelOle
|
|
540
490
|
|
541
491
|
# @private
|
542
492
|
def generate_workbook file_name # :deprecated: #
|
543
|
-
workbook_class.open(file_name, :
|
493
|
+
workbook_class.open(file_name, if_absent: :create, force: {excel: self})
|
544
494
|
end
|
545
495
|
|
546
496
|
# sets DisplayAlerts in a block
|
@@ -580,37 +530,30 @@ module RobustExcelOle
|
|
580
530
|
return if calculation_mode.nil?
|
581
531
|
@properties[:calculation] = calculation_mode
|
582
532
|
calc_mode_changable = @ole_excel.Workbooks.Count > 0 && @ole_excel.Calculation.is_a?(Integer)
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
end
|
605
|
-
saved = []
|
606
|
-
@ole_excel.Workbooks.each { |w| saved << w.Saved }
|
607
|
-
@ole_excel.CalculateBeforeSave = false
|
608
|
-
@ole_excel.Calculation =
|
609
|
-
calculation_mode == :automatic ? XlCalculationAutomatic : XlCalculationManual
|
610
|
-
saved = []
|
611
|
-
@ole_excel.Workbooks.each { |w| saved << w.Saved }
|
533
|
+
return unless calc_mode_changable
|
534
|
+
retain_saved_workbooks do
|
535
|
+
begin
|
536
|
+
best_wb_to_make_visible = @ole_excel.Workbooks.sort_by {|wb|
|
537
|
+
score =
|
538
|
+
(wb.Saved ? 0 : 40) + # an unsaved workbooks is most likely the main workbook
|
539
|
+
(wb.ReadOnly ? 0 : 20) + # the main wb is usually writable
|
540
|
+
case wb.Name.split(".").last.downcase
|
541
|
+
when "xlsm" then 10 # the main workbook is more likely to have macros
|
542
|
+
when "xls" then 8
|
543
|
+
when "xlsx" then 4
|
544
|
+
when "xlam" then -2 # libraries are not normally the main workbook
|
545
|
+
else 0
|
546
|
+
end
|
547
|
+
score
|
548
|
+
}.last
|
549
|
+
best_wb_to_make_visible.Windows(1).Visible = true
|
550
|
+
rescue => e
|
551
|
+
trace "error setting calculation=#{calculation_mode} msg: " + e.message
|
552
|
+
trace e.backtrace
|
553
|
+
# continue on errors here, failing would usually disrupt too much
|
612
554
|
end
|
613
|
-
|
555
|
+
@ole_excel.CalculateBeforeSave = false
|
556
|
+
@ole_excel.Calculation = calculation_mode == :automatic ? XlCalculationAutomatic : XlCalculationManual
|
614
557
|
end
|
615
558
|
end
|
616
559
|
|
@@ -628,7 +571,6 @@ module RobustExcelOle
|
|
628
571
|
# sets calculation mode in a block
|
629
572
|
def with_calculation(calculation_mode)
|
630
573
|
return unless calculation_mode
|
631
|
-
|
632
574
|
old_calculation_mode = @ole_excel.Calculation
|
633
575
|
begin
|
634
576
|
self.calculation = calculation_mode
|
@@ -639,35 +581,39 @@ module RobustExcelOle
|
|
639
581
|
end
|
640
582
|
|
641
583
|
# set options in this Excel instance
|
642
|
-
def for_this_instance(options)
|
643
|
-
set_options(options)
|
644
|
-
end
|
645
|
-
|
646
584
|
def set_options(options)
|
647
585
|
@properties ||= { }
|
648
586
|
PROPERTIES.each do |property|
|
649
587
|
method = (property.to_s + '=').to_sym
|
650
|
-
|
588
|
+
send(method, options[property])
|
651
589
|
end
|
652
590
|
end
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
591
|
+
|
592
|
+
alias for_this_instance set_options # :deprecated: #
|
593
|
+
|
594
|
+
# @return [Enumerator] traversing all workbook objects
|
595
|
+
def each
|
596
|
+
if block_given?
|
597
|
+
ole_workbooks.lazy.each do |ole_workbook|
|
598
|
+
yield workbook_class.new(ole_workbook)
|
599
|
+
end
|
600
|
+
else
|
601
|
+
to_enum(:each).lazy
|
602
|
+
end
|
657
603
|
end
|
658
604
|
|
605
|
+
# @return [Array] all workbook objects
|
659
606
|
def workbooks
|
660
|
-
|
661
|
-
ole_workbooks.map {|ole_workbook| workbook_class.new(ole_workbook) }
|
607
|
+
to_a
|
662
608
|
end
|
663
609
|
|
664
|
-
# traverses
|
610
|
+
# traverses all workbooks and sets options if provided
|
665
611
|
def each_workbook(opts = { })
|
666
|
-
ole_workbooks.each do |ow|
|
612
|
+
ole_workbooks.lazy.each do |ow|
|
667
613
|
wb = workbook_class.new(ow, opts)
|
668
614
|
block_given? ? (yield wb) : wb
|
669
615
|
end
|
670
|
-
end
|
616
|
+
end
|
671
617
|
|
672
618
|
def each_workbook_with_index(opts = { }, offset = 0)
|
673
619
|
i = offset
|
@@ -677,6 +623,8 @@ module RobustExcelOle
|
|
677
623
|
end
|
678
624
|
end
|
679
625
|
|
626
|
+
alias for_all_workbooks each_workbook # :deprecated: #
|
627
|
+
|
680
628
|
def focus
|
681
629
|
self.visible = true
|
682
630
|
# if not Windows10 then
|
@@ -692,11 +640,11 @@ module RobustExcelOle
|
|
692
640
|
@workbook ||= workbook_class.new(@ole_excel.ActiveWorkbook) if @ole_excel.Workbooks.Count > 0
|
693
641
|
end
|
694
642
|
|
695
|
-
|
643
|
+
alias active_workbook workbook
|
696
644
|
|
697
645
|
# @private
|
698
646
|
def to_s
|
699
|
-
|
647
|
+
"#<Excel: #{hwnd}#{ ("not alive" unless alive?)}>"
|
700
648
|
end
|
701
649
|
|
702
650
|
# @private
|
@@ -743,23 +691,20 @@ module RobustExcelOle
|
|
743
691
|
private
|
744
692
|
|
745
693
|
def method_missing(name, *args)
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
end
|
754
|
-
else
|
755
|
-
begin
|
756
|
-
@ole_excel.send(name, *args)
|
757
|
-
rescue NoMethodError
|
758
|
-
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
759
|
-
end
|
694
|
+
super unless name.to_s[0,1] =~ /[A-Z]/
|
695
|
+
raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
|
696
|
+
if ::ERRORMESSAGE_JRUBY_BUG
|
697
|
+
begin
|
698
|
+
@ole_excel.send(name, *args)
|
699
|
+
rescue Java::OrgRacobCom::ComFailException
|
700
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
760
701
|
end
|
761
702
|
else
|
762
|
-
|
703
|
+
begin
|
704
|
+
@ole_excel.send(name, *args)
|
705
|
+
rescue NoMethodError
|
706
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
707
|
+
end
|
763
708
|
end
|
764
709
|
end
|
765
710
|
end
|