robust_excel_ole 1.31 → 1.32

Sign up to get free protection for your applications and to get access to all the features.
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