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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +20 -1
  3. data/README.rdoc +118 -18
  4. data/___dummy_workbook.xls +0 -0
  5. data/benchmarking/creek_example.rb +1 -1
  6. data/benchmarking/roo_example.rb +1 -1
  7. data/benchmarking/simple_xlsx_reader_example.rb +1 -1
  8. data/benchmarking/spreadsheet_example.rb +1 -1
  9. data/docs/README_excel.rdoc +16 -24
  10. data/docs/README_listobjects.rdoc +176 -0
  11. data/docs/README_open.rdoc +12 -12
  12. data/docs/README_ranges.rdoc +72 -55
  13. data/docs/README_save_close.rdoc +3 -3
  14. data/docs/README_sheet.rdoc +18 -13
  15. data/examples/example_ruby_library.rb +2 -2
  16. data/examples/introductory_examples/example_range.rb +2 -2
  17. data/examples/modifying_sheets/example_access_sheets_and_cells.rb +6 -6
  18. data/examples/modifying_sheets/example_add_names.rb +1 -1
  19. data/examples/modifying_sheets/example_concating.rb +1 -1
  20. data/examples/modifying_sheets/example_copying.rb +2 -2
  21. data/examples/modifying_sheets/example_listobjects.rb +86 -0
  22. data/examples/modifying_sheets/example_naming.rb +1 -1
  23. data/examples/modifying_sheets/example_ranges.rb +1 -1
  24. data/examples/open_save_close/example_control_to_excel.rb +1 -1
  25. data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
  26. data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
  27. data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
  28. data/examples/open_save_close/example_if_unsaved_forget.rb +3 -3
  29. data/examples/open_save_close/example_if_unsaved_forget_more.rb +4 -4
  30. data/examples/open_save_close/example_read_only.rb +1 -1
  31. data/examples/open_save_close/example_simple.rb +1 -1
  32. data/examples/open_save_close/example_unobtrusively.rb +3 -3
  33. data/lib/robust_excel_ole/address_tool.rb +54 -44
  34. data/lib/robust_excel_ole/base.rb +4 -6
  35. data/lib/robust_excel_ole/bookstore.rb +2 -16
  36. data/lib/robust_excel_ole/cell.rb +16 -21
  37. data/lib/robust_excel_ole/excel.rb +131 -186
  38. data/lib/robust_excel_ole/general.rb +82 -55
  39. data/lib/robust_excel_ole/list_object.rb +182 -109
  40. data/lib/robust_excel_ole/list_row.rb +65 -38
  41. data/lib/robust_excel_ole/range.rb +125 -93
  42. data/lib/robust_excel_ole/range_owners.rb +52 -66
  43. data/lib/robust_excel_ole/version.rb +1 -1
  44. data/lib/robust_excel_ole/workbook.rb +168 -176
  45. data/lib/robust_excel_ole/worksheet.rb +177 -141
  46. data/robust_excel_ole.gemspec +4 -3
  47. data/spec/bookstore_spec.rb +2 -3
  48. data/spec/cell_spec.rb +9 -9
  49. data/spec/data/more_data/workbook.xls +0 -0
  50. data/spec/excel_spec.rb +132 -85
  51. data/spec/general_spec.rb +47 -15
  52. data/spec/list_object_spec.rb +258 -145
  53. data/spec/list_row_spec.rb +218 -0
  54. data/spec/range_spec.rb +76 -29
  55. data/spec/spec_helper.rb +15 -1
  56. data/spec/workbook_spec.rb +75 -34
  57. data/spec/workbook_specs/workbook_all_spec.rb +2 -1
  58. data/spec/workbook_specs/workbook_misc_spec.rb +20 -13
  59. data/spec/workbook_specs/workbook_open_spec.rb +47 -45
  60. data/spec/workbook_specs/workbook_save_spec.rb +21 -22
  61. data/spec/workbook_specs/workbook_sheet_spec.rb +3 -3
  62. data/spec/workbook_specs/workbook_unobtr_spec.rb +303 -303
  63. data/spec/worksheet_spec.rb +522 -318
  64. 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,:r1c1)
20
+ transform_address(address, :r1c1)
22
21
  end
23
22
 
24
23
  def as_a1(address)
25
- transform_address(address,:a1)
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,:int_range)
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
- if address.size == 1
41
- comp1, comp2 = address[0].split(':')
42
- a1_expr = /^(([A-Z]+[0-9]+)|([A-Z]+$)|([0-9]+))$/
43
- is_a1 = comp1 =~ a1_expr && (comp2.nil? || comp2 =~ a1_expr)
44
- r1c1_expr = /^(([A-Z]\[?-?[0-9]+\]?[A-Z]\[?-?[0-9]+\]?)|([A-Z]\[?-?[0-9]+\]?)|([A-Z]\[?-?[0-9]+\]?))$/
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==:r1c1
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
- def r1c1_string(letter,int_range,type)
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
- (self.methods - Object.methods).sort
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 do |f|
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]} => #{e[1]}"
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 = { :prefer_writable => true })
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
- result = []
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
- alias_method :v, :value
24
- alias_method :v=, :value=
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:" + " (#{@ole_range.Row},#{@ole_range.Column})" + ">"
33
+ "#<Cell: (#{@ole_range.Row},#{@ole_range.Column})>"
34
34
  end
35
35
 
36
36
  # @private
37
37
  def inspect
38
- self.to_s[0..-2] + " #{@ole_range.Parent.Name}" + ">"
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
- if name.to_s[0,1] =~ /[A-Z]/
46
- if ::ERRORMESSAGE_JRUBY_BUG
47
- begin
48
- #@ole_cell.send(name, *args)
49
- @ole_range.send(name, *args)
50
- rescue Java::OrgRacobCom::ComFailException
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
- super
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(:reuse => false))
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(:reuse => true))
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
- ole_xl = win32ole_excel unless win32ole_excel.nil?
74
- options = { :reuse => true }.merge(options)
75
- if options[:reuse] == true && ole_xl.nil?
76
- ole_xl = current_ole_excel
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
- begin
92
- reused = options[:reuse] && stored && stored.alive?
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 = {:visible => false, :displayalerts => :if_visible}.merge(
115
- {:visible => @properties[:visible], :displayalerts => @properties[:displayalerts]}).merge(opts)
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 = workbook_class.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
- ole_workbooks = begin
151
- @ole_excel.Workbooks
152
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
153
- if msg.message =~ /failed to get Dispatch Interface/
154
- raise ExcelDamaged, 'Excel instance not alive or damaged'
155
- else
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
- unsaved_workbooks = []
172
- begin
173
- @ole_excel.Workbooks.each { |w| unsaved_workbooks << w unless w.Saved || w.ReadOnly }
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 = { :if_unsaved => :raise })
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, 'user canceled or runtime error'
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 = { :if_unsaved => :raise }, &blk)
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(:if_unsaved => options[:if_unsaved])
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 = begin
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 = { :if_unsaved => :raise })
281
+ def close(options = { if_unsaved: :raise })
298
282
  finishing_living_excel = alive?
299
283
  if finishing_living_excel
300
- hwnd = (begin
301
- @ole_excel.Hwnd
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
- begin
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.excels_number
387
- processes = WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process')
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.known_excels_number
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 = known_excel_instance
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
- result = []
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, :if_absent => :create, :force => {:excel => self})
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
- if calc_mode_changable
584
- retain_saved_workbooks do
585
- begin
586
- best_wb_to_make_visible = @ole_excel.Workbooks.sort_by {|wb|
587
- score =
588
- (wb.Saved ? 0 : 40) + # an unsaved workbooks is most likely the main workbook
589
- (wb.ReadOnly ? 0 : 20) + # the main wb is usually writable
590
- case wb.Name.split(".").last.downcase
591
- when "xlsm" then 10 # the main workbook is more likely to have macros
592
- when "xls" then 8
593
- when "xlsx" then 4
594
- when "xlam" then -2 # libraries are not normally the main workbook
595
- else 0
596
- end
597
- score
598
- }.last
599
- best_wb_to_make_visible.Windows(1).Visible = true
600
- rescue => e
601
- trace "error setting calculation=#{calculation_mode} msg: " + e.message
602
- trace e.backtrace
603
- # continue on errors here, failing would usually disrupt too much
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
- #(1..@ole_excel.Workbooks.Count).each { |i| @ole_excel.Workbooks(i).Saved = true if saved[i - 1] }
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
- self.send(method, options[property])
588
+ send(method, options[property])
651
589
  end
652
590
  end
653
-
654
- # set options in all workbooks
655
- def for_all_workbooks(options)
656
- each_workbook(options)
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
- #ole_workbooks.map {|ole_workbook| ole_workbook.to_reo }
661
- ole_workbooks.map {|ole_workbook| workbook_class.new(ole_workbook) }
607
+ to_a
662
608
  end
663
609
 
664
- # traverses over all workbooks and sets options if provided
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
- alias_method :active_workbook, :workbook
643
+ alias active_workbook workbook
696
644
 
697
645
  # @private
698
646
  def to_s
699
- '#<Excel: ' + hwnd.to_s + ('not alive' unless alive?).to_s + '>'
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
- if name.to_s[0,1] =~ /[A-Z]/
747
- raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
748
- if ::ERRORMESSAGE_JRUBY_BUG
749
- begin
750
- @ole_excel.send(name, *args)
751
- rescue Java::OrgRacobCom::ComFailException => msg
752
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
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
- super
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