robust_excel_ole 1.31 → 1.32
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.
- 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
|