robust_excel_ole 1.27 → 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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +36 -2
  3. data/README.rdoc +121 -19
  4. data/___dummy_workbook.xls +0 -0
  5. data/benchmarking/creek_example.rb +1 -1
  6. data/benchmarking/reo_example.rb +1 -1
  7. data/benchmarking/reo_example1.rb +1 -1
  8. data/benchmarking/reo_example2.rb +1 -1
  9. data/benchmarking/roo_example.rb +1 -1
  10. data/benchmarking/simple_xlsx_reader_example.rb +1 -1
  11. data/benchmarking/spreadsheet_example.rb +1 -1
  12. data/bin/jreo +19 -0
  13. data/bin/reo +19 -0
  14. data/docs/README_excel.rdoc +16 -24
  15. data/docs/README_listobjects.rdoc +176 -0
  16. data/docs/README_open.rdoc +20 -16
  17. data/docs/README_ranges.rdoc +72 -55
  18. data/docs/README_save_close.rdoc +3 -3
  19. data/docs/README_sheet.rdoc +19 -20
  20. data/examples/example_ruby_library.rb +2 -2
  21. data/examples/introductory_examples/example_open.rb +11 -0
  22. data/examples/introductory_examples/example_range.rb +2 -2
  23. data/examples/modifying_sheets/example_access_sheets_and_cells.rb +6 -6
  24. data/examples/modifying_sheets/example_add_names.rb +1 -1
  25. data/examples/modifying_sheets/example_concating.rb +1 -1
  26. data/examples/modifying_sheets/example_copying.rb +2 -2
  27. data/examples/modifying_sheets/example_listobjects.rb +86 -0
  28. data/examples/modifying_sheets/example_naming.rb +1 -1
  29. data/examples/modifying_sheets/example_ranges.rb +1 -1
  30. data/examples/open_save_close/example_control_to_excel.rb +1 -1
  31. data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
  32. data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
  33. data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
  34. data/examples/open_save_close/example_if_unsaved_forget.rb +3 -3
  35. data/examples/open_save_close/example_if_unsaved_forget_more.rb +4 -4
  36. data/examples/open_save_close/example_read_only.rb +1 -1
  37. data/examples/open_save_close/example_simple.rb +1 -1
  38. data/examples/open_save_close/example_unobtrusively.rb +3 -3
  39. data/lib/robust_excel_ole.rb +19 -16
  40. data/lib/robust_excel_ole/address_tool.rb +54 -44
  41. data/lib/robust_excel_ole/base.rb +9 -6
  42. data/lib/robust_excel_ole/bookstore.rb +3 -17
  43. data/lib/robust_excel_ole/cell.rb +17 -22
  44. data/lib/robust_excel_ole/cygwin.rb +2 -0
  45. data/lib/robust_excel_ole/excel.rb +136 -201
  46. data/lib/robust_excel_ole/general.rb +249 -238
  47. data/lib/robust_excel_ole/list_object.rb +186 -210
  48. data/lib/robust_excel_ole/list_row.rb +155 -0
  49. data/lib/robust_excel_ole/range.rb +130 -94
  50. data/lib/robust_excel_ole/range_owners.rb +54 -135
  51. data/lib/robust_excel_ole/version.rb +1 -1
  52. data/lib/robust_excel_ole/workbook.rb +230 -196
  53. data/lib/robust_excel_ole/worksheet.rb +254 -133
  54. data/lib/spec_helper.rb +1 -1
  55. data/robust_excel_ole.gemspec +4 -3
  56. data/spec/address_tool_spec.rb +2 -2
  57. data/spec/base_spec.rb +19 -17
  58. data/spec/bookstore_spec.rb +3 -4
  59. data/spec/cell_spec.rb +10 -10
  60. data/spec/cygwin_spec.rb +1 -1
  61. data/spec/data/more_data/workbook.xls +0 -0
  62. data/spec/excel_spec.rb +133 -86
  63. data/spec/general_spec.rb +79 -18
  64. data/spec/list_object_spec.rb +259 -81
  65. data/spec/list_row_spec.rb +218 -0
  66. data/spec/range_spec.rb +75 -41
  67. data/spec/spec_helper.rb +16 -2
  68. data/spec/workbook_spec.rb +87 -46
  69. data/spec/workbook_specs/workbook_all_spec.rb +9 -28
  70. data/spec/workbook_specs/workbook_close_spec.rb +1 -1
  71. data/spec/workbook_specs/workbook_misc_spec.rb +52 -45
  72. data/spec/workbook_specs/workbook_open_spec.rb +103 -50
  73. data/spec/workbook_specs/workbook_save_spec.rb +22 -23
  74. data/spec/workbook_specs/workbook_sheet_spec.rb +4 -4
  75. data/spec/workbook_specs/workbook_subclass_spec.rb +1 -1
  76. data/spec/workbook_specs/workbook_unobtr_spec.rb +553 -395
  77. data/spec/worksheet_spec.rb +544 -308
  78. metadata +38 -3
  79. data/lib/reo_console.rb +0 -42
@@ -20,7 +20,7 @@ begin
20
20
  book.excel.visible = true # make current Excel visible
21
21
  sheet = book.sheet(1) # access a worksheet
22
22
  sleep 1
23
- sheet[1,1] = sheet[1,1].Value == "simple" ? "complex" : "simple" # change a cell
23
+ sheet[1,1] = sheet[1,1] == "simple" ? "complex" : "simple" # change a cell
24
24
  sleep 1
25
25
  book.save # simple save
26
26
  begin
@@ -12,14 +12,14 @@ begin
12
12
  simple_file = dir + 'workbook.xls'
13
13
  book = Workbook.open(simple_file, :visible => true) # open a workbook, make Excel visible
14
14
  old_sheet = book.sheet(1)
15
- p "1st cell: #{old_sheet[1,1].Value}"
15
+ p "1st cell: #{old_sheet[1,1]}"
16
16
  sleep 2
17
17
  Workbook.unobtrusively(simple_file) do |book| # modify the book and keep its status unchanged
18
18
  sheet = book.sheet(1)
19
- sheet[1,1] = sheet[1,1].Value == "simple" ? "complex" : "simple"
19
+ sheet[1,1] = sheet[1,1] == "simple" ? "complex" : "simple"
20
20
  end
21
21
  new_sheet = book.sheet(1)
22
- p "1st cell: #{new_sheet[1,1].Value}"
22
+ p "1st cell: #{new_sheet[1,1]}"
23
23
  p "book saved" if book.Saved
24
24
  book.close # close the workbook
25
25
  ensure
@@ -1,22 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  if RUBY_PLATFORM =~ /java/
2
4
  require 'jruby-win32ole'
3
5
  else
4
6
  require 'win32ole'
5
7
  end
6
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/base')
7
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/general')
8
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/vba_objects')
9
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/range_owners')
10
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/address_tool')
11
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/excel')
12
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/bookstore')
13
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/workbook')
14
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/worksheet')
15
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/cell')
16
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/range')
17
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/list_object')
18
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/cygwin') if RUBY_PLATFORM =~ /cygwin/
19
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/version')
20
8
 
21
- #include RobustExcelOle
22
- include General
9
+ require_relative 'robust_excel_ole/general'
10
+ require_relative 'robust_excel_ole/base'
11
+ require_relative 'robust_excel_ole/vba_objects'
12
+ require_relative 'robust_excel_ole/range_owners'
13
+ require_relative 'robust_excel_ole/address_tool'
14
+ require_relative 'robust_excel_ole/excel'
15
+ require_relative 'robust_excel_ole/bookstore'
16
+ require_relative 'robust_excel_ole/workbook'
17
+ require_relative 'robust_excel_ole/worksheet'
18
+ require_relative 'robust_excel_ole/cell'
19
+ require_relative 'robust_excel_ole/range'
20
+ require_relative 'robust_excel_ole/list_row'
21
+ require_relative 'robust_excel_ole/list_object'
22
+ require_relative 'robust_excel_ole/cygwin' if RUBY_PLATFORM =~ /cygwin/
23
+ require_relative 'robust_excel_ole/version'
24
+
25
+ General.init_reo_for_win32ole
@@ -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
@@ -65,9 +65,11 @@ module RobustExcelOle
65
65
 
66
66
  class Base
67
67
 
68
+ include General
69
+
68
70
  # @private
69
71
  def own_methods
70
- (self.methods - Object.methods).sort
72
+ (methods - Object.methods).sort
71
73
  end
72
74
 
73
75
  # @private
@@ -97,12 +99,10 @@ module RobustExcelOle
97
99
  def self.puts_hash(hash)
98
100
  hash.each do |e|
99
101
  if e[1].is_a?(Hash)
100
- puts "#{e[0]} =>"
101
- e[1].each do |f|
102
- puts " #{f[0]} => #{f[1]}"
103
- end
102
+ puts "#{e[0]}: "
103
+ e[1].each{ |f| puts " #{f[0]}: #{f[1]}" }
104
104
  else
105
- puts "#{e[0]} => #{e[1]}"
105
+ puts "#{e[0]}: #{e[1]}"
106
106
  end
107
107
  end
108
108
  end
@@ -110,3 +110,6 @@ module RobustExcelOle
110
110
  end
111
111
 
112
112
  end
113
+
114
+ REO = RobustExcelOle
115
+
@@ -4,7 +4,7 @@ module RobustExcelOle
4
4
 
5
5
  # @private
6
6
  class Bookstore < Base
7
-
7
+
8
8
  def initialize
9
9
  @filename2books ||= Hash.new { |hash, key| hash[key] = [] }
10
10
  @hidden_excel_instance = nil
@@ -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
@@ -1,13 +1,13 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- require File.join(File.dirname(__FILE__), './range')
3
+ require_relative 'range'
4
4
 
5
5
  module RobustExcelOle
6
6
 
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
@@ -1,3 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  module RobustExcelOle
2
4
  module Cygwin
3
5
  require 'Win32API'
@@ -15,6 +15,9 @@ module RobustExcelOle
15
15
  # See https://docs.microsoft.com/en-us/office/vba/api/excel.application(object)#methods
16
16
 
17
17
  class Excel < VbaObjects
18
+
19
+ include Enumerable
20
+
18
21
  attr_reader :ole_excel
19
22
  attr_reader :properties
20
23
  attr_reader :address_tool
@@ -33,7 +36,7 @@ module RobustExcelOle
33
36
  # @option options [Boolean] :screenupdating
34
37
  # @return [Excel] a new Excel instance
35
38
  def self.create(options = {})
36
- new(options.merge(:reuse => false))
39
+ new(options.merge(reuse: false))
37
40
  end
38
41
 
39
42
  # connects to the current (first opened) Excel instance, if such a running Excel instance exists
@@ -44,7 +47,7 @@ module RobustExcelOle
44
47
  # @option options [Boolean] :screenupdating
45
48
  # @return [Excel] an Excel instance
46
49
  def self.current(options = {})
47
- new(options.merge(:reuse => true))
50
+ new(options.merge(reuse: true))
48
51
  end
49
52
 
50
53
  # returns an Excel instance
@@ -69,10 +72,11 @@ module RobustExcelOle
69
72
  options = win32ole_excel
70
73
  win32ole_excel = nil
71
74
  end
72
- ole_xl = win32ole_excel unless win32ole_excel.nil?
73
- options = { :reuse => true }.merge(options)
74
- if options[:reuse] == true && ole_xl.nil?
75
- 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
76
80
  end
77
81
  connected = (not ole_xl.nil?) && win32ole_excel.nil?
78
82
  ole_xl ||= WIN32OLE.new('Excel.Application')
@@ -86,14 +90,9 @@ module RobustExcelOle
86
90
  WIN32OLE.const_load(ole_xl, RobustExcelOle) unless RobustExcelOle.const_defined?(:CONSTANTS)
87
91
  @@hwnd2excel[hwnd] = WeakRef.new(result)
88
92
  end
89
-
90
- begin
91
- reused = options[:reuse] && stored && stored.alive?
92
- unless reused || connected
93
- options = { :displayalerts => :if_visible, :visible => false, :screenupdating => true }.merge(options)
94
- end
95
- result.set_options(options)
96
- 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)
97
96
  result
98
97
  end
99
98
 
@@ -110,15 +109,12 @@ module RobustExcelOle
110
109
  # @return [Excel] an Excel instance
111
110
  def recreate(opts = {})
112
111
  unless alive?
113
- opts = {:visible => false, :displayalerts => :if_visible}.merge(
114
- {: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)
115
114
  @ole_excel = WIN32OLE.new('Excel.Application')
116
115
  set_options(opts)
117
116
  if opts[:reopen_workbooks]
118
- books = workbook_class.books
119
- books.each do |book|
120
- book.reopen if !book.alive? && book.excel.alive? && book.excel == self
121
- end
117
+ workbook_class.books.each{ |book| book.reopen if !book.alive? && book.excel.alive? && book.excel == self }
122
118
  end
123
119
  end
124
120
  self
@@ -146,34 +142,29 @@ module RobustExcelOle
146
142
  end
147
143
 
148
144
  def ole_workbooks
149
- ole_workbooks = begin
150
- @ole_excel.Workbooks
151
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
152
- if msg.message =~ /failed to get Dispatch Interface/
153
- raise ExcelDamaged, 'Excel instance not alive or damaged'
154
- else
155
- raise ExcelREOError, 'workbooks could not be determined'
156
- 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}"
157
151
  end
158
152
  end
159
153
 
160
154
  public
161
155
 
162
156
  # @private
157
+ # returns unsaved workbooks (win32ole objects)
163
158
  def self.contains_unsaved_workbooks?
164
159
  !Excel.current.unsaved_workbooks.empty?
165
160
  end
166
161
 
167
- # returns unsaved workbooks (win32ole objects)
168
162
  # @private
163
+ # returns unsaved workbooks (win32ole objects)
169
164
  def unsaved_workbooks
170
- unsaved_workbooks = []
171
- begin
172
- @ole_excel.Workbooks.each { |w| unsaved_workbooks << w unless w.Saved || w.ReadOnly }
173
- rescue RuntimeError => msg
174
- raise ExcelDamaged, 'Excel instance not alive or damaged' if msg.message =~ /failed to get Dispatch Interface/
175
- end
176
- 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/
177
168
  end
178
169
 
179
170
  # closes workbooks
@@ -184,7 +175,7 @@ module RobustExcelOle
184
175
  # :save -> saves the workbooks before closing
185
176
  # :alert -> let Excel do it
186
177
  # @private
187
- def close_workbooks(options = { :if_unsaved => :raise })
178
+ def close_workbooks(options = { if_unsaved: :raise })
188
179
  return unless alive?
189
180
 
190
181
  weak_wkbks = @ole_excel.Workbooks
@@ -207,11 +198,12 @@ module RobustExcelOle
207
198
  "\nHint: Valid values are :raise, :forget, :save and :alert"
208
199
  end
209
200
  end
201
+
210
202
  begin
211
203
  @ole_excel.Workbooks.Close
212
204
  rescue
213
205
  if $!.message =~ /kann nicht zugeordnet werden/ or $!.message =~ /800A03EC/
214
- raise ExcelREOError, 'user canceled or runtime error'
206
+ raise ExcelREOError, "user canceled or runtime error"
215
207
  else
216
208
  raise UnexpectedREOError, "unknown WIN32OLERuntimeError: #{msg.message}"
217
209
  end
@@ -234,7 +226,7 @@ module RobustExcelOle
234
226
  # :forget -> closes the excel instance without saving the workbooks
235
227
  # :alert -> give control to Excel
236
228
  # @option options [Proc] block
237
- def self.close_all(options = { :if_unsaved => :raise }, &blk)
229
+ def self.close_all(options = { if_unsaved: :raise }, &blk)
238
230
  options[:if_unsaved] = blk if blk
239
231
  finished_number = error_number = overall_number = 0
240
232
  first_error = nil
@@ -242,10 +234,9 @@ module RobustExcelOle
242
234
  if excel
243
235
  begin
244
236
  overall_number += 1
245
- finished_number += excel.close(:if_unsaved => options[:if_unsaved])
237
+ finished_number += excel.close(if_unsaved: options[:if_unsaved])
246
238
  rescue
247
239
  first_error = $!
248
- #trace "error when finishing #{$!}"
249
240
  error_number += 1
250
241
  end
251
242
  end
@@ -268,19 +259,13 @@ module RobustExcelOle
268
259
  old_error_number = error_number
269
260
  9.times do |_index|
270
261
  sleep 0.1
271
- excel = begin
272
- new(WIN32OLE.connect('Excel.Application'))
273
- rescue
274
- nil
275
- end
262
+ excel = new(WIN32OLE.connect('Excel.Application')) rescue nil
276
263
  finishing_action.call(excel) if excel
277
264
  free_all_ole_objects unless (error_number > 0) && (options[:if_unsaved] == :raise)
278
265
  break unless excel
279
266
  break if error_number > old_error_number # + 3
280
267
  end
281
-
282
268
  raise first_error if ((options[:if_unsaved] == :raise) && first_error) || (first_error.class == OptionInvalid)
283
-
284
269
  [finished_number, error_number]
285
270
  end
286
271
 
@@ -293,19 +278,13 @@ module RobustExcelOle
293
278
  # :save -> saves the workbooks before closing
294
279
  # :forget -> closes the Excel instance without saving the workbooks
295
280
  # :alert -> Excel takes over
296
- def close(options = { :if_unsaved => :raise })
281
+ def close(options = { if_unsaved: :raise })
297
282
  finishing_living_excel = alive?
298
283
  if finishing_living_excel
299
- hwnd = (begin
300
- @ole_excel.Hwnd
301
- rescue
302
- nil
303
- end)
304
- close_workbooks(:if_unsaved => options[:if_unsaved])
284
+ hwnd = @ole_excel.Hwnd rescue nil
285
+ close_workbooks(if_unsaved: options[:if_unsaved])
305
286
  @ole_excel.Quit
306
- if false && defined?(weak_wkbks) && weak_wkbks.weakref_alive?
307
- weak_wkbks.ole_free
308
- end
287
+ weak_wkbks.ole_free if false && defined?(weak_wkbks) && weak_wkbks.weakref_alive?
309
288
  weak_xl = WeakRef.new(@ole_excel)
310
289
  else
311
290
  weak_xl = nil
@@ -319,21 +298,10 @@ module RobustExcelOle
319
298
  pid_puffer = ' ' * 32
320
299
  process_id.call(hwnd, pid_puffer)
321
300
  pid = pid_puffer.unpack('L')[0]
322
- begin
323
- Process.kill('KILL', pid)
324
- rescue
325
- # trace "kill_error: #{$!}"
326
- end
301
+ Process.kill('KILL', pid) rescue nil
327
302
  end
328
303
  @@hwnd2excel.delete(hwnd)
329
- if weak_xl.weakref_alive?
330
- # if WIN32OLE.ole_reference_count(weak_xlapp) > 0
331
- begin
332
- weak_xl.ole_free
333
- rescue
334
- # trace "weakref_probl_olefree"
335
- end
336
- end
304
+ weak_xl.ole_free if weak_xl.weakref_alive?
337
305
  end
338
306
  weak_xl ? 1 : 0
339
307
  end
@@ -382,15 +350,42 @@ module RobustExcelOle
382
350
  number
383
351
  end
384
352
 
385
- def self.excels_number
386
- processes = WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process')
387
- 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
388
355
  end
389
356
 
390
- def self.known_excels_number
357
+ def self.known_instance_count
391
358
  @@hwnd2excel.size
392
359
  end
393
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
+
394
389
  private
395
390
 
396
391
  # returns a Win32OLE object that represents a Excel instance to which Excel connects
@@ -398,7 +393,7 @@ module RobustExcelOle
398
393
  # if this Excel instance is being closed, then Excel creates a new Excel instance
399
394
  def self.current_ole_excel
400
395
  if ::CONNECT_EXCEL_JRUBY_BUG
401
- result = known_excel_instance
396
+ result = known_running_instance
402
397
  if result.nil?
403
398
  if excels_number > 0
404
399
  dummy_ole_workbook = WIN32OLE.connect(General.absolute_path('___dummy_workbook.xls')) rescue nil
@@ -427,17 +422,6 @@ module RobustExcelOle
427
422
  result
428
423
  end
429
424
 
430
- # returns an Excel instance
431
- def self.known_excel_instance
432
- @@hwnd2excel.each do |hwnd, wr_excel|
433
- if wr_excel.weakref_alive?
434
- excel = wr_excel.__getobj__
435
- return excel if excel.alive?
436
- end
437
- end
438
- nil
439
- end
440
-
441
425
  def self.hwnd2excel(hwnd)
442
426
  excel_weakref = @@hwnd2excel[hwnd]
443
427
  if excel_weakref
@@ -457,35 +441,6 @@ module RobustExcelOle
457
441
 
458
442
  public
459
443
 
460
- # returns all Excel objects for all Excel instances opened with RobustExcelOle
461
- def self.known_excel_instances
462
- pid2excel = {}
463
- @@hwnd2excel.each do |hwnd,wr_excel|
464
- next unless wr_excel.weakref_alive?
465
-
466
- excel = wr_excel.__getobj__
467
- process_id = Win32API.new('user32', 'GetWindowThreadProcessId', %w[I P], 'I')
468
- pid_puffer = ' ' * 32
469
- process_id.call(hwnd, pid_puffer)
470
- pid = pid_puffer.unpack('L')[0]
471
- pid2excel[pid] = excel
472
- end
473
- processes = WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process')
474
- processes.select { |p| Excel.new(pid2excel[p.ProcessId]) if p.Name == 'EXCEL.EXE' && pid2excel.include?(p.ProcessId) }
475
- result = []
476
- processes.each do |p|
477
- next unless p.Name == 'EXCEL.EXE'
478
-
479
- if pid2excel.include?(p.ProcessId)
480
- excel = pid2excel[p.ProcessId]
481
- result << excel
482
- end
483
- # how to connect to an (interactively opened) Excel instance and get a WIN32OLE object?
484
- # after that, lift it to an Excel object
485
- end
486
- result
487
- end
488
-
489
444
  # @private
490
445
  def excel
491
446
  self
@@ -523,15 +478,11 @@ module RobustExcelOle
523
478
 
524
479
  # returns unsaved workbooks in known (not opened by user) Excel instances
525
480
  # @private
526
- def self.unsaved_known_workbooks
527
- result = []
528
- @@hwnd2excel.each do |_hwnd,wr_excel|
529
- excel = wr_excel.__getobj__ if wr_excel.weakref_alive?
530
- result << excel.unsaved_workbooks
531
- end
532
- 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
533
483
  end
534
484
 
485
+
535
486
  # @private
536
487
  def print_workbooks
537
488
  self.Workbooks.each { |w| trace "#{w.Name} #{w}" }
@@ -539,7 +490,7 @@ module RobustExcelOle
539
490
 
540
491
  # @private
541
492
  def generate_workbook file_name # :deprecated: #
542
- workbook_class.open(file_name, :if_absent => :create, :force => {:excel => self})
493
+ workbook_class.open(file_name, if_absent: :create, force: {excel: self})
543
494
  end
544
495
 
545
496
  # sets DisplayAlerts in a block
@@ -579,37 +530,30 @@ module RobustExcelOle
579
530
  return if calculation_mode.nil?
580
531
  @properties[:calculation] = calculation_mode
581
532
  calc_mode_changable = @ole_excel.Workbooks.Count > 0 && @ole_excel.Calculation.is_a?(Integer)
582
- if calc_mode_changable
583
- retain_saved_workbooks do
584
- begin
585
- best_wb_to_make_visible = @ole_excel.Workbooks.sort_by {|wb|
586
- score =
587
- (wb.Saved ? 0 : 40) + # an unsaved workbooks is most likely the main workbook
588
- (wb.ReadOnly ? 0 : 20) + # the main wb is usually writable
589
- case wb.Name.split(".").last.downcase
590
- when "xlsm" then 10 # the main workbook is more likely to have macros
591
- when "xls" then 8
592
- when "xlsx" then 4
593
- when "xlam" then -2 # libraries are not normally the main workbook
594
- else 0
595
- end
596
- score
597
- }.last
598
- best_wb_to_make_visible.Windows(1).Visible = true
599
- rescue => e
600
- trace "error setting calculation=#{calculation_mode} msg: " + e.message
601
- trace e.backtrace
602
- # continue on errors here, failing would usually disrupt too much
603
- end
604
- saved = []
605
- @ole_excel.Workbooks.each { |w| saved << w.Saved }
606
- @ole_excel.CalculateBeforeSave = false
607
- @ole_excel.Calculation =
608
- calculation_mode == :automatic ? XlCalculationAutomatic : XlCalculationManual
609
- saved = []
610
- @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
611
554
  end
612
- #(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
613
557
  end
614
558
  end
615
559
 
@@ -627,7 +571,6 @@ module RobustExcelOle
627
571
  # sets calculation mode in a block
628
572
  def with_calculation(calculation_mode)
629
573
  return unless calculation_mode
630
-
631
574
  old_calculation_mode = @ole_excel.Calculation
632
575
  begin
633
576
  self.calculation = calculation_mode
@@ -638,35 +581,39 @@ module RobustExcelOle
638
581
  end
639
582
 
640
583
  # set options in this Excel instance
641
- def for_this_instance(options)
642
- set_options(options)
643
- end
644
-
645
584
  def set_options(options)
646
585
  @properties ||= { }
647
586
  PROPERTIES.each do |property|
648
587
  method = (property.to_s + '=').to_sym
649
- self.send(method, options[property])
588
+ send(method, options[property])
650
589
  end
651
590
  end
652
-
653
- # set options in all workbooks
654
- def for_all_workbooks(options)
655
- 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
656
603
  end
657
604
 
605
+ # @return [Array] all workbook objects
658
606
  def workbooks
659
- #ole_workbooks.map {|ole_workbook| ole_workbook.to_reo }
660
- ole_workbooks.map {|ole_workbook| workbook_class.new(ole_workbook) }
607
+ to_a
661
608
  end
662
609
 
663
- # traverses over all workbooks and sets options if provided
610
+ # traverses all workbooks and sets options if provided
664
611
  def each_workbook(opts = { })
665
- ole_workbooks.each do |ow|
612
+ ole_workbooks.lazy.each do |ow|
666
613
  wb = workbook_class.new(ow, opts)
667
614
  block_given? ? (yield wb) : wb
668
615
  end
669
- end
616
+ end
670
617
 
671
618
  def each_workbook_with_index(opts = { }, offset = 0)
672
619
  i = offset
@@ -676,6 +623,8 @@ module RobustExcelOle
676
623
  end
677
624
  end
678
625
 
626
+ alias for_all_workbooks each_workbook # :deprecated: #
627
+
679
628
  def focus
680
629
  self.visible = true
681
630
  # if not Windows10 then
@@ -684,32 +633,18 @@ module RobustExcelOle
684
633
  # Win32API.new("user32","SetForegroundWindow","","I").call
685
634
  # end
686
635
  end
687
-
688
- # returns the value of a range
689
- # @param [String] name the name of a range
690
- # @returns [Variant] the value of the range
691
- def [] name
692
- namevalue_glob(name)
693
- end
694
-
695
- # sets the value of a range
696
- # @param [String] name the name of the range
697
- # @param [Variant] value the contents of the range
698
- def []=(name, value)
699
- set_namevalue_glob(name, value)
700
- end
701
-
636
+
702
637
  # @private
703
638
  # returns active workbook
704
639
  def workbook
705
640
  @workbook ||= workbook_class.new(@ole_excel.ActiveWorkbook) if @ole_excel.Workbooks.Count > 0
706
641
  end
707
642
 
708
- alias_method :active_workbook, :workbook
643
+ alias active_workbook workbook
709
644
 
710
645
  # @private
711
646
  def to_s
712
- '#<Excel: ' + hwnd.to_s + ('not alive' unless alive?).to_s + '>'
647
+ "#<Excel: #{hwnd}#{ ("not alive" unless alive?)}>"
713
648
  end
714
649
 
715
650
  # @private
@@ -717,6 +652,9 @@ module RobustExcelOle
717
652
  to_s
718
653
  end
719
654
 
655
+ using ParentRefinement
656
+ using StringRefinement
657
+
720
658
  # @private
721
659
  def self.workbook_class
722
660
  @workbook_class ||= begin
@@ -753,23 +691,20 @@ module RobustExcelOle
753
691
  private
754
692
 
755
693
  def method_missing(name, *args)
756
- if name.to_s[0,1] =~ /[A-Z]/
757
- raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
758
- if ::ERRORMESSAGE_JRUBY_BUG
759
- begin
760
- @ole_excel.send(name, *args)
761
- rescue Java::OrgRacobCom::ComFailException => msg
762
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
763
- end
764
- else
765
- begin
766
- @ole_excel.send(name, *args)
767
- rescue NoMethodError
768
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
769
- 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}"
770
701
  end
771
702
  else
772
- super
703
+ begin
704
+ @ole_excel.send(name, *args)
705
+ rescue NoMethodError
706
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
707
+ end
773
708
  end
774
709
  end
775
710
  end