robust_excel_ole 1.14 → 1.18.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +34 -1
  3. data/README.rdoc +20 -6
  4. data/___dummy_workbook.xls +0 -0
  5. data/docs/README_excel.rdoc +7 -1
  6. data/docs/README_open.rdoc +45 -20
  7. data/docs/README_ranges.rdoc +11 -2
  8. data/examples/example_ruby_library.rb +27 -0
  9. data/extconf.rb +13 -0
  10. data/lib/robust_excel_ole.rb +4 -3
  11. data/lib/robust_excel_ole/{address.rb → address_tool.rb} +23 -22
  12. data/lib/robust_excel_ole/{reo_common.rb → base.rb} +2 -90
  13. data/lib/robust_excel_ole/bookstore.rb +14 -77
  14. data/lib/robust_excel_ole/cell.rb +30 -18
  15. data/lib/robust_excel_ole/excel.rb +105 -64
  16. data/lib/robust_excel_ole/general.rb +40 -15
  17. data/lib/robust_excel_ole/range.rb +42 -19
  18. data/lib/robust_excel_ole/range_owners.rb +40 -25
  19. data/lib/robust_excel_ole/vba_objects.rb +30 -0
  20. data/lib/robust_excel_ole/version.rb +1 -1
  21. data/lib/robust_excel_ole/workbook.rb +310 -336
  22. data/lib/robust_excel_ole/worksheet.rb +51 -25
  23. data/robust_excel_ole.gemspec +1 -0
  24. data/spec/address_tool_spec.rb +175 -0
  25. data/spec/{reo_common_spec.rb → base_spec.rb} +19 -34
  26. data/spec/bookstore_spec.rb +3 -3
  27. data/spec/cell_spec.rb +67 -25
  28. data/spec/data/more_data/workbook.xls +0 -0
  29. data/spec/excel_spec.rb +137 -369
  30. data/spec/general_spec.rb +21 -27
  31. data/spec/range_spec.rb +57 -3
  32. data/spec/workbook_spec.rb +11 -79
  33. data/spec/workbook_specs/workbook_misc_spec.rb +29 -40
  34. data/spec/workbook_specs/workbook_open_spec.rb +570 -30
  35. data/spec/workbook_specs/workbook_unobtr_spec.rb +440 -136
  36. data/spec/worksheet_spec.rb +36 -4
  37. metadata +11 -7
  38. data/spec/address_spec.rb +0 -174
@@ -3,24 +3,47 @@
3
3
  module General
4
4
 
5
5
  IS_JRUBY_PLATFORM = (RUBY_PLATFORM =~ /java/)
6
- JRUBY_BUG_CONNECT = IS_JRUBY_PLATFORM && true
7
- JRUBY_BUG_COPYSHEETS = IS_JRUBY_PLATFORM && true
8
- JRUBY_BUG_ERRORMESSAGE = IS_JRUBY_PLATFORM && true
9
- JRUBY_BUG_CONNECTEXCEL = IS_JRUBY_PLATFORM && true
10
- JRUBY_BUG_RANGES = IS_JRUBY_PLATFORM && true
11
-
6
+ ::EXPANDPATH_JRUBY_BUG = IS_JRUBY_PLATFORM && true
7
+ ::CONNECT_JRUBY_BUG = IS_JRUBY_PLATFORM && true
8
+ ::COPYSHEETS_JRUBY_BUG = IS_JRUBY_PLATFORM && true
9
+ ::ERRORMESSAGE_JRUBY_BUG = IS_JRUBY_PLATFORM && true
10
+ ::CONNECT_EXCEL_JRUBY_BUG = IS_JRUBY_PLATFORM && true
11
+ ::RANGES_JRUBY_BUG = IS_JRUBY_PLATFORM && true
12
+
13
+ @private
14
+ def network2hostnamesharepath(filename)
15
+ network = WIN32OLE.new('WScript.Network')
16
+ drives = network.enumnetworkdrives
17
+ drive_letter, filename_after_drive_letter = filename.split(':')
18
+ # if filename starts with a drive letter not c and this drive exists,
19
+ # then determine the corresponding host_share_path
20
+ default_drive = File.absolute_path(".")[0]
21
+ if drive_letter != default_drive && drive_letter != filename
22
+ for i in 0 .. drives.Count-1
23
+ next if i % 2 == 1
24
+ if drives.Item(i).gsub(':','') == drive_letter
25
+ hostname_share = drives.Item(i+1) #.gsub('\\','/').gsub('//','')
26
+ break
27
+ end
28
+ end
29
+ hostname_share + filename_after_drive_letter if hostname_share
30
+ else
31
+ return filename
32
+ end
33
+ end
12
34
 
13
35
  # @private
14
- def absolute_path(file)
36
+ def absolute_path(file)
37
+ file[0,2] = './' if ::EXPANDPATH_JRUBY_BUG && file =~ /[A-Z]:[^\/]/
15
38
  file = File.expand_path(file)
16
39
  file = RobustExcelOle::Cygwin.cygpath('-w', file) if RUBY_PLATFORM =~ /cygwin/
17
40
  WIN32OLE.new('Scripting.FileSystemObject').GetAbsolutePathName(file).tr('/','\\')
18
41
  end
19
42
 
20
43
  # @private
21
- def canonize(filename)
44
+ def canonize(filename)
22
45
  raise TypeREOError, "No string given to canonize, but #{filename.inspect}" unless filename.is_a?(String)
23
-
46
+ filename = network2hostnamesharepath(filename)
24
47
  normalize(filename).downcase
25
48
  end
26
49
 
@@ -72,26 +95,28 @@ end
72
95
 
73
96
  # @private
74
97
  class WIN32OLE
75
-
76
- include RobustExcelOle
77
98
 
78
- # promoting WIN32OLE objects to RobustExcelOle objects
99
+ # type-lifting WIN32OLE objects to RobustExcelOle objects
79
100
  def to_reo
80
- class2method = [{Excel => :Hwnd}, {Workbook => :FullName}, {Worksheet => :Copy}, {Range => :Address}]
101
+ class2method = [{Excel => :Hwnd}, {Workbook => :FullName}, {Worksheet => :Copy}, {RobustExcelOle::Range => :Row}]
81
102
  class2method.each do |element|
82
103
  classname = element.first.first
83
104
  method = element.first.last
84
105
  begin
85
106
  self.send(method)
86
- return classname.new(self)
107
+ if classname == RobustExcelOle::Range && self.Rows.Count == 1 && self.Columns.Count == 1
108
+ return Cell.new(self)
109
+ else
110
+ return classname.new(self)
111
+ end
87
112
  rescue
88
113
  next
89
114
  end
90
115
  end
116
+ raise TypeREOError, "given object cannot be type-lifted to a RobustExcelOle object"
91
117
  end
92
118
  end
93
119
 
94
-
95
120
  # @private
96
121
  class ::String
97
122
  def / path_part
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+
2
3
  module RobustExcelOle
3
4
 
4
5
  # This class essentially wraps a Win32Ole Range object.
@@ -6,28 +7,44 @@ module RobustExcelOle
6
7
  # that you would apply for a Range object.
7
8
  # See https://docs.microsoft.com/en-us/office/vba/api/excel.worksheet#methods
8
9
 
9
- class Range < REOCommon
10
+ class Range < VbaObjects
10
11
  include Enumerable
11
12
  attr_reader :ole_range
12
13
  attr_reader :worksheet
13
14
 
14
- def initialize(win32_range)
15
+ def initialize(win32_range, worksheet = nil)
15
16
  @ole_range = win32_range
16
- @worksheet = worksheet_class.new(self.Parent)
17
+ @worksheet = worksheet ? worksheet : worksheet_class.new(self.Parent)
18
+ #@worksheet = worksheet_class.new(self.Parent)
17
19
  end
18
20
 
19
21
  def each
20
- @ole_range.each do |row_or_column|
21
- yield RobustExcelOle::Cell.new(row_or_column)
22
+ @ole_range.each_with_index do |ole_cell, index|
23
+ yield cell(index){ole_cell}
22
24
  end
23
25
  end
24
26
 
27
+ def [] index
28
+ cell(index) {
29
+ @ole_range.Cells.Item(index + 1)
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def cell(index)
36
+ @cells ||= []
37
+ @cells[index + 1] ||= RobustExcelOle::Cell.new(yield,@worksheet)
38
+ end
39
+
40
+ public
41
+
25
42
  # returns flat array of the values of a given range
26
43
  # @params [Range] a range
27
44
  # @returns [Array] the values
28
45
  def values(range = nil)
29
46
  #result = map { |x| x.Value }.flatten
30
- result_unflatten = if !JRUBY_BUG_RANGES
47
+ result_unflatten = if !::RANGES_JRUBY_BUG
31
48
  map { |x| x.v }
32
49
  else
33
50
  self.v
@@ -44,11 +61,11 @@ module RobustExcelOle
44
61
 
45
62
  def v
46
63
  begin
47
- if !JRUBY_BUG_RANGES
64
+ if !::RANGES_JRUBY_BUG
48
65
  self.Value
49
66
  else
50
67
  address_r1c1 = self.AddressLocal(true,true,XlR1C1)
51
- row, col = Address.int_range(address_r1c1)
68
+ row, col = address_tool.as_integer_ranges(address_r1c1)
52
69
  values = []
53
70
  row.each do |r|
54
71
  values_col = []
@@ -65,11 +82,11 @@ module RobustExcelOle
65
82
 
66
83
  def v=(value)
67
84
  begin
68
- if !JRUBY_BUG_RANGES
85
+ if !::RANGES_JRUBY_BUG
69
86
  ole_range.Value = value
70
87
  else
71
88
  address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
72
- row, col = Address.int_range(address_r1c1)
89
+ row, col = address_tool.as_integer_ranges(address_r1c1)
73
90
  row.each_with_index do |r,i|
74
91
  col.each_with_index do |c,j|
75
92
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
@@ -82,10 +99,8 @@ module RobustExcelOle
82
99
  end
83
100
  end
84
101
 
85
- def [] index
86
- @cells = []
87
- @cells[index + 1] = RobustExcelOle::Cell.new(@ole_range.Cells.Item(index + 1))
88
- end
102
+ alias_method :value, :v
103
+ alias_method :value=, :v=
89
104
 
90
105
  # copies a range
91
106
  # @params [Address or Address-Array] address or upper left position of the destination range
@@ -115,8 +130,7 @@ module RobustExcelOle
115
130
  { }
116
131
  end
117
132
  end
118
- rows, columns = Address.int_range(dest_address)
119
- #dest_sheet = @worksheet if dest_sheet == :__not_provided
133
+ rows, columns = address_tool.as_integer_ranges(dest_address)
120
134
  dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
121
135
  dest_range_address = if (not dest_address_is_position)
122
136
  [rows.min..rows.max,columns.min..columns.max]
@@ -168,7 +182,7 @@ module RobustExcelOle
168
182
  # @options [Worksheet] the destination worksheet
169
183
  # @options [Hash] options: :transpose, :values_only
170
184
  def copy_special(dest_address, dest_sheet = :__not_provided, options = { })
171
- rows, columns = Address.int_range(dest_address)
185
+ rows, columns = address_tool.as_integer_ranges(dest_address)
172
186
  dest_sheet = @worksheet if dest_sheet == :__not_provided
173
187
  dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
174
188
  dest_range_address = if (not dest_address_is_position)
@@ -220,6 +234,11 @@ module RobustExcelOle
220
234
  self.Address == other_range.Address
221
235
  end
222
236
 
237
+ # @private
238
+ def excel
239
+ @worksheet.workbook.excel
240
+ end
241
+
223
242
  # @private
224
243
  def self.worksheet_class
225
244
  @worksheet_class ||= begin
@@ -237,10 +256,9 @@ module RobustExcelOle
237
256
 
238
257
  private
239
258
 
240
- # @private
241
259
  def method_missing(name, *args)
242
260
  if name.to_s[0,1] =~ /[A-Z]/
243
- if JRUBY_BUG_ERRORMESSAGE
261
+ if ::ERRORMESSAGE_JRUBY_BUG
244
262
  begin
245
263
  @ole_range.send(name, *args)
246
264
  rescue Java::OrgRacobCom::ComFailException
@@ -258,4 +276,9 @@ module RobustExcelOle
258
276
  end
259
277
  end
260
278
  end
279
+
280
+ # @private
281
+ class RangeNotCopied < MiscREOError
282
+ end
283
+
261
284
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RobustExcelOle
4
4
 
5
- class RangeOwners < REOCommon
5
+ class RangeOwners < VbaObjects
6
6
 
7
7
  # returns the contents of a range with given name
8
8
  # if the name could not be found or the value could not be determined,
@@ -22,12 +22,13 @@ module RobustExcelOle
22
22
  raise
23
23
  end
24
24
  ole_range = name_obj.RefersToRange
25
+ worksheet = self if self.is_a?(Worksheet)
25
26
  value = begin
26
27
  #name_obj.RefersToRange.Value
27
- if !JRUBY_BUG_RANGES
28
+ if !::RANGES_JRUBY_BUG
28
29
  ole_range.Value
29
30
  else
30
- values = RobustExcelOle::Range.new(ole_range).v
31
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
31
32
  (values.size==1 && values.first.size==1) ? values.first.first : values
32
33
  end
33
34
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -39,10 +40,10 @@ module RobustExcelOle
39
40
  #sheet.Evaluate(name_obj.Name).Value
40
41
  # does it result in a range?
41
42
  ole_range = sheet.Evaluate(name_obj.Name)
42
- if !JRUBY_BUG_RANGES
43
+ if !::RANGES_JRUBY_BUG
43
44
  ole_range.Value
44
45
  else
45
- values = RobustExcelOle::Range.new(ole_range).v
46
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
46
47
  (values.size==1 && values.first.size==1) ? values.first.first : values
47
48
  end
48
49
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -61,7 +62,8 @@ module RobustExcelOle
61
62
  # sets the contents of a range
62
63
  # @param [String] name the name of a range
63
64
  # @param [Variant] value the contents of the range
64
- def set_namevalue_glob(name, value, opts = { }) # opts is deprecated
65
+ # @option opts [Symbol] :color the color of the cell when set
66
+ def set_namevalue_glob(name, value, opts = { })
65
67
  begin
66
68
  name_obj = begin
67
69
  name_object(name)
@@ -69,13 +71,12 @@ module RobustExcelOle
69
71
  raise
70
72
  end
71
73
  ole_range = name_object(name).RefersToRange
72
- workbook.color_if_modified = opts[:color] unless opts[:color].nil?
73
- ole_range.Interior.ColorIndex = workbook.color_if_modified unless workbook.color_if_modified.nil?
74
- if !JRUBY_BUG_RANGES
74
+ ole_range.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
75
+ if !::RANGES_JRUBY_BUG
75
76
  ole_range.Value = value
76
77
  else
77
78
  address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
78
- row, col = Address.int_range(address_r1c1)
79
+ row, col = address_tool.as_integer_ranges(address_r1c1)
79
80
  row.each_with_index do |r,i|
80
81
  col.each_with_index do |c,j|
81
82
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value )
@@ -105,11 +106,12 @@ module RobustExcelOle
105
106
  raise NameNotFound, "name #{name.inspect} not in #{self.inspect}"
106
107
  end
107
108
  begin
109
+ worksheet = self if self.is_a?(Worksheet)
108
110
  #value = ole_range.Value
109
- value = if !JRUBY_BUG_RANGES
111
+ value = if !::RANGES_JRUBY_BUG
110
112
  ole_range.Value
111
113
  else
112
- values = RobustExcelOle::Range.new(ole_range).v
114
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
113
115
  (values.size==1 && values.first.size==1) ? values.first.first : values
114
116
  end
115
117
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -127,21 +129,21 @@ module RobustExcelOle
127
129
  # assigns a value to a range given a locally defined name
128
130
  # @param [String] name the name of a range
129
131
  # @param [Variant] value the assigned value
130
- def set_namevalue(name, value, opts = { }) # opts is deprecated
132
+ # @option opts [Symbol] :color the color of the cell when set
133
+ def set_namevalue(name, value, opts = { })
131
134
  begin
132
- return set_namevalue_glob(name, value, opts) if self.is_a?(Workbook) # opts deprecated
135
+ return set_namevalue_glob(name, value, opts) if self.is_a?(Workbook)
133
136
  ole_range = self.Range(name)
134
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
137
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException, VBAMethodMissingError
135
138
  raise NameNotFound, "name #{name.inspect} not in #{self.inspect}"
136
139
  end
137
140
  begin
138
- workbook.color_if_modified = opts[:color] unless opts[:color].nil?
139
- ole_range.Interior.ColorIndex = workbook.color_if_modified unless workbook.color_if_modified.nil?
140
- if !JRUBY_BUG_RANGES
141
+ ole_range.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
142
+ if !::RANGES_JRUBY_BUG
141
143
  ole_range.Value = value
142
144
  else
143
145
  address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
144
- row, col = Address.int_range(address_r1c1)
146
+ row, col = address_tool.as_integer_ranges(address_r1c1)
145
147
  row.each_with_index do |r,i|
146
148
  col.each_with_index do |c,j|
147
149
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
@@ -180,10 +182,11 @@ module RobustExcelOle
180
182
  # @return [Range] a range
181
183
  def range(name_or_address, address2 = :__not_provided)
182
184
  begin
185
+ worksheet = self if self.is_a?(Worksheet)
183
186
  if address2 == :__not_provided
184
187
  range = if name_or_address.is_a?(String)
185
188
  begin
186
- RobustExcelOle::Range.new(name_object(name_or_address).RefersToRange)
189
+ RobustExcelOle::Range.new(name_object(name_or_address).RefersToRange, worksheet)
187
190
  rescue NameNotFound
188
191
  nil
189
192
  end
@@ -192,8 +195,8 @@ module RobustExcelOle
192
195
  if self.is_a?(Worksheet) && (range.nil? || (address2 != :__not_provided))
193
196
  address = name_or_address
194
197
  address = [name_or_address,address2] unless address2 == :__not_provided
195
- self.Names.Add('__dummy001',nil,true,nil,nil,nil,nil,nil,nil,'=' + Address.r1c1(address))
196
- range = RobustExcelOle::Range.new(name_object('__dummy001').RefersToRange)
198
+ self.Names.Add('__dummy001',nil,true,nil,nil,nil,nil,nil,nil,'=' + address_tool.as_r1c1(address))
199
+ range = RobustExcelOle::Range.new(name_object('__dummy001').RefersToRange, worksheet)
197
200
  self.Names.Item('__dummy001').Delete
198
201
  workbook = self.is_a?(Workbook) ? self : self.workbook
199
202
  workbook.save
@@ -216,7 +219,7 @@ module RobustExcelOle
216
219
  def add_name(name, addr, addr_deprecated = :__not_provided)
217
220
  addr = [addr,addr_deprecated] unless addr_deprecated == :__not_provided
218
221
  begin
219
- self.Names.Add(name, nil, true, nil, nil, nil, nil, nil, nil, '=' + Address.r1c1(addr))
222
+ self.Names.Add(name, nil, true, nil, nil, nil, nil, nil, nil, '=' + address_tool.as_r1c1(addr))
220
223
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
221
224
  raise RangeNotEvaluatable, "cannot add name #{name.inspect} to range #{addr.inspect}"
222
225
  end
@@ -257,13 +260,13 @@ module RobustExcelOle
257
260
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
258
261
  raise UnexpectedREOError, "name error in #{File.basename(self.stored_filename).inspect}"
259
262
  end
260
- end
263
+ end
261
264
 
262
265
  private
263
266
 
264
267
  def name_object(name)
265
268
  self.Names.Item(name)
266
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
269
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException, VBAMethodMissingError
267
270
  begin
268
271
  self.Parent.Names.Item(name)
269
272
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -273,4 +276,16 @@ module RobustExcelOle
273
276
 
274
277
  end
275
278
 
279
+ # @private
280
+ class NameNotFound < NamesREOError
281
+ end
282
+
283
+ # @private
284
+ class NameAlreadyExists < NamesREOError
285
+ end
286
+
287
+ # @private
288
+ class RangeNotCreated < MiscREOError
289
+ end
290
+
276
291
  end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module RobustExcelOle
4
+
5
+ class VbaObjects < Base
6
+
7
+ def to_reo
8
+ self
9
+ end
10
+
11
+ # @private
12
+ def address_tool
13
+ excel.address_tool
14
+ end
15
+
16
+ end
17
+
18
+ # @private
19
+ class RangeNotEvaluatable < MiscREOError
20
+ end
21
+
22
+ # @private
23
+ class OptionInvalid < MiscREOError
24
+ end
25
+
26
+ # @private
27
+ class ObjectNotAlive < MiscREOError
28
+ end
29
+
30
+ end
@@ -1,3 +1,3 @@
1
1
  module RobustExcelOle
2
- VERSION = "1.14"
2
+ VERSION = "1.18.1"
3
3
  end
@@ -12,33 +12,33 @@ module RobustExcelOle
12
12
  class Workbook < RangeOwners
13
13
 
14
14
  #include General
15
-
16
- attr_accessor :excel
17
- attr_accessor :ole_workbook
18
- attr_accessor :stored_filename
19
- attr_accessor :color_if_modified
20
- attr_accessor :was_open
21
- attr_reader :workbook
15
+
16
+ attr_reader :ole_workbook
17
+ attr_reader :excel
18
+ attr_reader :stored_filename
22
19
 
23
20
  alias ole_object ole_workbook
24
21
 
25
- DEFAULT_OPEN_OPTS = {
26
- :default => {:excel => :current},
22
+ CORE_DEFAULT_OPEN_OPTS = {
23
+ :default => {:excel => :current},
27
24
  :force => {},
28
- :update_links => :never,
25
+ :update_links => :never
26
+ }.freeze
27
+
28
+ DEFAULT_OPEN_OPTS = {
29
29
  :if_unsaved => :raise,
30
30
  :if_obstructed => :raise,
31
31
  :if_absent => :raise,
32
- :if_exists => :raise,
33
- :check_compatibility => false
34
- }.freeze
35
-
36
- CORE_DEFAULT_OPEN_OPTS = {
37
- :default => {:excel => :current}, :force => {}, :update_links => :never
38
- }.freeze
32
+ :if_exists => :raise
33
+ }.merge(CORE_DEFAULT_OPEN_OPTS).freeze
39
34
 
40
- ABBREVIATIONS = [[:default,:d], [:force, :f], [:excel, :e], [:visible, :v],
41
- [:if_obstructed, :if_blocked]].freeze
35
+ ABBREVIATIONS = [
36
+ [:default,:d],
37
+ [:force, :f],
38
+ [:excel, :e],
39
+ [:visible, :v],
40
+ [:if_obstructed, :if_blocked]
41
+ ].freeze
42
42
 
43
43
 
44
44
  # opens a workbook.
@@ -84,50 +84,59 @@ module RobustExcelOle
84
84
  # :visible true -> makes the workbook visible
85
85
  # :check_compatibility true -> check compatibility when saving
86
86
  # :update_links true -> user is being asked how to update links, false -> links are never updated
87
- # @return [Workbook] a representation of a workbook
88
- def self.new(file_or_workbook, opts = { }, &block)
89
- options = process_options(opts)
90
- if file_or_workbook.is_a? WIN32OLE
87
+ # @return [Workbook] a representation of a workbook
88
+ def self.new(file_or_workbook, opts = { })
89
+ process_options(opts)
90
+ case file_or_workbook
91
+ when NilClass
92
+ raise FileNameNotGiven, 'filename is nil'
93
+ when WIN32OLE
91
94
  file = file_or_workbook.Fullname.tr('\\','/')
92
- else
95
+ when Workbook
96
+ file = file_or_workbook.Fullname.tr('\\','/')
97
+ when String
93
98
  file = file_or_workbook
94
- raise(FileNameNotGiven, 'filename is nil') if file.nil?
95
- raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
99
+ raise FileNotFound, "file #{General.absolute_path(file).inspect} is a directory" if File.directory?(file)
100
+ else
101
+ raise TypeREOError, 'given object is neither a filename, a Win32ole, nor a Workbook object'
96
102
  end
97
103
  # try to fetch the workbook from the bookstore
104
+ set_was_open opts, file_or_workbook.is_a?(WIN32OLE)
98
105
  book = nil
99
- if options[:force][:excel] != :new
106
+ if opts[:force][:excel] != :new
100
107
  # if readonly is true, then prefer a book that is given in force_excel if this option is set
101
- forced_excel =
102
- (options[:force][:excel].nil? || options[:force][:excel] == :current) ?
103
- (excel_class.new(:reuse => true) if !JRUBY_BUG_CONNECT) : excel_of(options[:force][:excel])
108
+ forced_excel = begin
109
+ (opts[:force][:excel].nil? || opts[:force][:excel] == :current) ?
110
+ (excel_class.new(:reuse => true) if !::CONNECT_JRUBY_BUG) : opts[:force][:excel].to_reo.excel
111
+ rescue NoMethodError
112
+ raise TypeREOError, "provided Excel option value is neither an Excel object nor a valid option"
113
+ end
104
114
  begin
105
115
  book = if File.exists?(file)
106
- bookstore.fetch(file, :prefer_writable => !(options[:read_only]),
107
- :prefer_excel => (options[:read_only] ? forced_excel : nil))
116
+ bookstore.fetch(file, :prefer_writable => !(opts[:read_only]),
117
+ :prefer_excel => (opts[:read_only] ? forced_excel : nil))
108
118
  end
109
119
  rescue
110
- trace "#{$!.message}"
120
+ raise
121
+ #trace "#{$!.message}"
111
122
  end
112
123
  if book
113
- book.was_open = book.alive?
124
+ set_was_open opts, book.alive?
114
125
  # drop the fetched workbook if it shall be opened in another Excel instance
115
126
  # or the workbook is an unsaved workbook that should not be accepted
116
- if (options[:force][:excel].nil? || options[:force][:excel] == :current || forced_excel == book.excel) &&
117
- !(book.alive? && !book.saved && (options[:if_unsaved] != :accept))
118
- options[:force][:excel] = book.excel if book.excel && book.excel.alive?
119
- book.ensure_workbook(file,options)
120
- book.set_options(file,options)
127
+ if (opts[:force][:excel].nil? || opts[:force][:excel] == :current || forced_excel == book.excel) &&
128
+ !(book.alive? && !book.saved && (opts[:if_unsaved] != :accept))
129
+ opts[:force][:excel] = book.excel if book.excel && book.excel.alive?
130
+ book.ensure_workbook(file,opts)
131
+ book.send :apply_options, file, opts
121
132
  return book
122
133
  end
123
134
  end
124
135
  end
125
- super(file_or_workbook, options, &block)
136
+ super(file_or_workbook, opts)
126
137
  end
127
138
 
128
- def self.open(file_or_workbook, opts = { }, &block)
129
- new(file_or_workbook, opts, &block)
130
- end
139
+ singleton_class.send :alias_method, :open, :new
131
140
 
132
141
  # creates a new Workbook object, if a file name is given
133
142
  # Promotes the win32ole workbook to a Workbook object, if a win32ole-workbook is given
@@ -135,26 +144,23 @@ module RobustExcelOle
135
144
  # @param [Hash] opts
136
145
  # @option opts [Symbol] see above
137
146
  # @return [Workbook] a workbook
138
- def initialize(file_or_workbook, options = { }, &block)
147
+ def initialize(file_or_workbook, opts)
139
148
  if file_or_workbook.is_a? WIN32OLE
140
149
  @ole_workbook = file_or_workbook
141
- ole_excel = begin
142
- WIN32OLE.connect(@ole_workbook.Fullname).Application
143
- rescue
150
+ ole_excel = begin
151
+ @ole_workbook.Application
152
+ rescue WIN32OLERuntimeError
144
153
  raise ExcelREOError, 'could not determine the Excel instance'
145
154
  end
146
155
  @excel = excel_class.new(ole_excel)
147
- filename = file_or_workbook.Fullname.tr('\\','/')
156
+ filename = @ole_workbook.Fullname.tr('\\','/')
148
157
  else
149
158
  filename = file_or_workbook
150
- ensure_workbook(filename, options)
159
+ ensure_workbook(filename, opts)
151
160
  end
152
- set_options(filename, options)
153
- bookstore.store(self)
154
- @workbook = @excel.workbook = self
155
- r1c1_letters = @ole_workbook.Worksheets.Item(1).Cells.Item(1,1).Address(true,true,XlR1C1).gsub(/[0-9]/,'') #('ReferenceStyle' => XlR1C1).gsub(/[0-9]/,'')
156
- address_class.new(r1c1_letters)
157
- if block
161
+ apply_options(filename, opts)
162
+ store_myself
163
+ if block_given?
158
164
  begin
159
165
  yield self
160
166
  ensure
@@ -165,52 +171,46 @@ module RobustExcelOle
165
171
 
166
172
  private
167
173
 
168
- # translates abbreviations and synonyms and merges with default options
169
- def self.process_options(options, proc_opts = {:use_defaults => true})
170
- translator = proc do |opts|
171
- erg = {}
172
- opts.each do |key,value|
173
- new_key = key
174
- ABBREVIATIONS.each { |long,short| new_key = long if key == short }
175
- if value.is_a?(Hash)
176
- erg[new_key] = {}
177
- value.each do |k,v|
178
- new_k = k
179
- ABBREVIATIONS.each { |l,s| new_k = l if k == s }
180
- erg[new_key][new_k] = v
181
- end
182
- else
183
- erg[new_key] = value
184
- end
185
- end
186
- erg[:default] ||= {}
187
- erg[:force] ||= {}
188
- force_list = [:visible, :excel]
189
- erg.each { |key,value| erg[:force][key] = value if force_list.include?(key) }
190
- erg[:default][:excel] = erg[:default_excel] unless erg[:default_excel].nil?
191
- erg[:force][:excel] = erg[:force_excel] unless erg[:force_excel].nil?
192
- erg[:default][:excel] = :current if erg[:default][:excel] == :reuse || erg[:default][:excel] == :active
193
- erg[:force][:excel] = :current if erg[:force][:excel] == :reuse || erg[:force][:excel] == :active
194
- erg
195
- end
196
- opts = translator.call(options)
197
- default_open_opts = proc_opts[:use_defaults] ? DEFAULT_OPEN_OPTS : CORE_DEFAULT_OPEN_OPTS
198
- default_opts = translator.call(default_open_opts)
199
- opts = default_opts.merge(opts)
200
- opts[:default] = default_opts[:default].merge(opts[:default]) unless opts[:default].nil?
201
- opts[:force] = default_opts[:force].merge(opts[:force]) unless opts[:force].nil?
202
- opts
174
+ def self.set_was_open(hash, value)
175
+ hash[:was_open] = value if hash.has_key?(:was_open)
203
176
  end
204
177
 
205
- # returns an Excel object when given Excel, Workbook or Win32ole object representing a Workbook or an Excel
206
- # @private
207
- def self.excel_of(object)
208
- begin
209
- object = object.to_reo if object.is_a? WIN32OLE
210
- object.excel
211
- rescue
212
- raise TypeREOError, 'given object is neither an Excel, a Workbook, nor a Win32ole'
178
+ def set_was_open(hash, value)
179
+ self.class.set_was_open(hash, value)
180
+ end
181
+
182
+ def self.process_options(opts, proc_opts = {:use_defaults => true})
183
+ translate(opts)
184
+ default_opts = (proc_opts[:use_defaults] ? DEFAULT_OPEN_OPTS : CORE_DEFAULT_OPEN_OPTS).dup
185
+ translate(default_opts)
186
+ opts.merge!(default_opts) { |key, v1, v2| !v2.is_a?(Hash) ? v1 : v2.merge(v1 || {}) }
187
+ end
188
+
189
+ def self.translate(opts)
190
+ erg = {}
191
+ opts.each do |key,value|
192
+ new_key = key
193
+ ABBREVIATIONS.each { |long,short| new_key = long if key == short }
194
+ if value.is_a?(Hash)
195
+ erg[new_key] = {}
196
+ value.each do |k,v|
197
+ new_k = k
198
+ ABBREVIATIONS.each { |l,s| new_k = l if k == s }
199
+ erg[new_key][new_k] = v
200
+ end
201
+ else
202
+ erg[new_key] = value
203
+ end
213
204
  end
205
+ opts.merge!(erg)
206
+ opts[:default] ||= {}
207
+ opts[:force] ||= {}
208
+ force_list = [:visible, :excel]
209
+ opts.each { |key,value| opts[:force][key] = value if force_list.include?(key) }
210
+ opts[:default][:excel] = opts[:default_excel] unless opts[:default_excel].nil?
211
+ opts[:force][:excel] = opts[:force_excel] unless opts[:force_excel].nil?
212
+ opts[:default][:excel] = :current if opts[:default][:excel] == :reuse || opts[:default][:excel] == :active
213
+ opts[:force][:excel] = :current if opts[:force][:excel] == :reuse || opts[:force][:excel] == :active
214
214
  end
215
215
 
216
216
  public
@@ -219,93 +219,91 @@ module RobustExcelOle
219
219
  # ensures an excel but not for jruby if current Excel shall be used
220
220
  def ensure_excel(options)
221
221
  return if @excel && @excel.alive?
222
- excel_option = options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
222
+ excel_option = options[:force][:excel] || options[:default][:excel] || :current
223
223
  @excel = if excel_option == :new
224
224
  excel_class.new(:reuse => false)
225
- elsif excel_option.nil? || excel_option == :current
225
+ elsif excel_option == :current
226
226
  excel_class.new(:reuse => true)
227
+ elsif excel_option.respond_to?(:to_reo)
228
+ excel_option.to_reo.excel
227
229
  else
228
- self.class.excel_of(excel_option)
230
+ raise TypeREOError, "provided Excel option value is neither an Excel object nor a valid option"
229
231
  end
230
- raise ExcelREOError, "excel is not alive" unless @excel && @excel.alive?
232
+ raise ExcelREOError, "Excel is not alive" unless @excel && @excel.alive?
231
233
  end
232
234
 
235
+
233
236
  # @private
234
237
  def ensure_workbook(filename, options)
235
- unless @ole_workbook && alive?
236
- filename = @stored_filename ? @stored_filename : filename
237
- manage_nonexisting_file(filename,options)
238
- excel_option = options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
239
- ensure_excel(options)
240
- workbooks = @excel.Workbooks
241
- @ole_workbook = workbooks.Item(File.basename(filename)) rescue nil if @ole_workbook.nil?
242
- if @ole_workbook
243
- @was_open = true
244
- manage_blocking_or_unsaved_workbook(filename,options)
245
- else
246
- if excel_option.nil? || excel_option == :current &&
247
- (!JRUBY_BUG_CONNECT || filename[0] != '/')
248
- connect(filename,options)
249
- else
250
- open_or_create_workbook(filename,options)
251
- end
252
- end
238
+ set_was_open options, true
239
+ return if (@ole_workbook && alive? && (options[:read_only].nil? || @ole_workbook.ReadOnly == options[:read_only]))
240
+ set_was_open options, false
241
+ if options[:if_unsaved]==:accept &&
242
+ ((options[:read_only]==true && self.ReadOnly==false) || (options[:read_only]==false && self.ReadOnly==true))
243
+ raise OptionInvalid, ":if_unsaved:accept and change of read-only mode is not possible"
253
244
  end
245
+ filename = @stored_filename ? @stored_filename : filename
246
+ manage_nonexisting_file(filename,options)
247
+ excel_option = options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
248
+ ensure_excel(options)
249
+ workbooks = @excel.Workbooks
250
+ @ole_workbook = workbooks.Item(File.basename(filename)) rescue nil if @ole_workbook.nil?
251
+ if @ole_workbook && alive?
252
+ set_was_open options, true
253
+ manage_blocking_or_unsaved_workbook(filename,options)
254
+ open_or_create_workbook(filename,options) if @ole_workbook.ReadOnly != options[:read_only]
255
+ else
256
+ if (excel_option.nil? || excel_option == :current) &&
257
+ !(::CONNECT_JRUBY_BUG && filename[0] == '/')
258
+ connect(filename,options)
259
+ else
260
+ open_or_create_workbook(filename,options)
261
+ end
262
+ end
254
263
  end
255
264
 
256
- # @private
257
- def set_options(filename, options)
265
+ private
266
+
267
+ # applies options to workbook named with filename
268
+ def apply_options(filename, options)
269
+ # changing read-only mode
270
+ #ensure_workbook(filename, options) if options[:read_only] && options[:read_only] != @ole_workbook.ReadOnly
258
271
  if (!options[:read_only].nil?) && options[:read_only] != @ole_workbook.ReadOnly
259
- #raise OptionInvalid, ":if_unsaved:accept and changing read-only mode is not possible" if options[:if_unsaved]==:accept
260
- @excel.with_displayalerts(false) { @ole_workbook.Close }
261
- @ole_workbook = nil
262
- open_or_create_workbook(filename, options)
272
+ ensure_workbook(filename, options)
263
273
  end
264
274
  retain_saved do
265
275
  self.visible = options[:force][:visible].nil? ? @excel.Visible : options[:force][:visible]
266
276
  @excel.calculation = options[:calculation] unless options[:calculation].nil?
267
277
  @ole_workbook.CheckCompatibility = options[:check_compatibility] unless options[:check_compatibility].nil?
268
- end
278
+ end
269
279
  end
270
280
 
271
-
272
- private
273
-
274
- # @private
275
281
  # connects to an unknown workbook
276
- def connect(filename,options)
277
- excels_number = excel_class.excels_number
278
- workbooks_number = if excels_number>0
279
- excel_class.current.Workbooks.Count
280
- else 0
281
- end
282
- abs_filename = General.absolute_path(filename)
282
+ def connect(filename, options)
283
+ workbooks_number = excel_class.excels_number==0 ? 0 : excel_class.current.Workbooks.Count
283
284
  @ole_workbook = begin
284
- WIN32OLE.connect(abs_filename)
285
+ WIN32OLE.connect(General.absolute_path(filename))
285
286
  rescue
286
287
  if $!.message =~ /moniker/
287
- raise WorkbookConnectingBlockingError
288
- else
289
- raise WorkbookConnectingUnknownError
288
+ raise WorkbookConnectingBlockingError, "some workbook is blocking when connecting"
289
+ else
290
+ raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook"
290
291
  end
291
292
  end
292
293
  ole_excel = begin
293
294
  @ole_workbook.Application
294
295
  rescue
295
296
  if $!.message =~ /dispid/
296
- raise WorkbookConnectingUnsavedError
297
- else
298
- raise WorkbookConnectingUnknownError
297
+ raise WorkbookConnectingUnsavedError, "workbook is unsaved when connecting"
298
+ else
299
+ raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook"
299
300
  end
300
301
  end
302
+ set_was_open options, (ole_excel.Workbooks.Count == workbooks_number)
301
303
  @excel = excel_class.new(ole_excel)
302
- excels_number_after = excel_class.excels_number
303
- workbooks_number_after = ole_excel.Workbooks.Count
304
- @was_open = (excels_number_after==excels_number) && (workbooks_number_after==workbooks_number)
305
304
  end
306
305
 
307
- # @private
308
- def manage_nonexisting_file(filename,options)
306
+ def manage_nonexisting_file(filename, options)
309
307
  return if File.exist?(filename)
310
308
  abs_filename = General.absolute_path(filename)
311
309
  if options[:if_absent] == :create
@@ -323,11 +321,12 @@ module RobustExcelOle
323
321
  end
324
322
  end
325
323
 
326
- # @private
327
- def manage_blocking_or_unsaved_workbook(filename,options)
328
- obstructed_by_other_book = if (File.basename(filename) == File.basename(@ole_workbook.Fullname))
329
- General.absolute_path(filename) != @ole_workbook.Fullname
330
- end
324
+ def manage_blocking_or_unsaved_workbook(filename, options)
325
+ filename = General.absolute_path(filename)
326
+ filename = General.canonize(filename)
327
+ previous_file = General.canonize(@ole_workbook.Fullname)
328
+ obstructed_by_other_book = (File.basename(filename) == File.basename(previous_file)) &&
329
+ (File.dirname(filename) != File.dirname(previous_file))
331
330
  if obstructed_by_other_book
332
331
  # workbook is being obstructed by a workbook with same name and different path
333
332
  manage_blocking_workbook(filename,options)
@@ -339,8 +338,7 @@ module RobustExcelOle
339
338
  end
340
339
  end
341
340
 
342
- # @private
343
- def manage_blocking_workbook(filename,options)
341
+ def manage_blocking_workbook(filename, options)
344
342
  case options[:if_obstructed]
345
343
  when :raise
346
344
  raise WorkbookBlocked, "can't open workbook #{filename},
@@ -354,7 +352,8 @@ module RobustExcelOle
354
352
  manage_saving_workbook(filename, options)
355
353
  when :close_if_saved
356
354
  if !@ole_workbook.Saved
357
- raise WorkbookBlocked, "workbook with the same name in a different path is unsaved: #{@ole_workbook.Fullname.tr('\\','/')}"
355
+ raise WorkbookBlocked, "workbook with the same name in a different path is unsaved: #{@ole_workbook.Fullname.tr('\\','/')}" +
356
+ "\nHint: Use the option :if_blocked => :save to save the workbook"
358
357
  else
359
358
  manage_forgetting_workbook(filename, options)
360
359
  end
@@ -366,15 +365,14 @@ module RobustExcelOle
366
365
  end
367
366
  end
368
367
 
369
- # @private
370
- def manage_unsaved_workbook(filename,options)
368
+ def manage_unsaved_workbook(filename, options)
371
369
  case options[:if_unsaved]
372
370
  when :raise
373
371
  raise WorkbookNotSaved, "workbook is already open but not saved: #{File.basename(filename).inspect}" +
374
372
  "\nHint: Save the workbook or open the workbook using option :if_unsaved with values :forget and :accept to
375
373
  close the unsaved workbook and reopen it, or to let the unsaved workbook open, respectively"
376
374
  when :forget
377
- manage_forgetting_workbook(filename,options)
375
+ manage_forgetting_workbook(filename, options)
378
376
  when :accept
379
377
  # do nothing
380
378
  when :save
@@ -389,29 +387,26 @@ module RobustExcelOle
389
387
  end
390
388
  end
391
389
 
392
- # @private
393
390
  def manage_forgetting_workbook(filename, options)
394
391
  @excel.with_displayalerts(false) { @ole_workbook.Close }
395
392
  @ole_workbook = nil
396
393
  open_or_create_workbook(filename, options)
397
394
  end
398
395
 
399
- # @private
400
396
  def manage_saving_workbook(filename, options)
401
397
  save unless @ole_workbook.Saved
402
398
  manage_forgetting_workbook(filename, options)
403
399
  end
404
400
 
405
- # @private
406
401
  def manage_new_excel(filename, options)
407
402
  @excel = excel_class.new(:reuse => false)
408
403
  @ole_workbook = nil
409
404
  open_or_create_workbook(filename, options)
410
405
  end
411
406
 
412
- # @private
413
- def open_or_create_workbook(filename, options)
414
- return if @ole_workbook && options[:if_unsaved] != :alert && options[:if_unsaved] != :excel
407
+ def open_or_create_workbook(filename, options)
408
+ return if @ole_workbook && options[:if_unsaved] != :alert && options[:if_unsaved] != :excel &&
409
+ (options[:read_only].nil? || options[:read_only]==@ole_workbook.ReadOnly )
415
410
  begin
416
411
  abs_filename = General.absolute_path(filename)
417
412
  begin
@@ -426,7 +421,7 @@ module RobustExcelOle
426
421
  updatelinks_vba(options[:update_links]),
427
422
  options[:read_only] )
428
423
  end
429
- rescue WIN32OLERuntimeError => msg
424
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
430
425
  # for Excel2007: for option :if_unsaved => :alert and user cancels: this error appears?
431
426
  # if yes: distinguish these events
432
427
  raise UnexpectedREOError, "cannot open workbook: #{msg.message} #{msg.backtrace}"
@@ -435,29 +430,27 @@ module RobustExcelOle
435
430
  # workaround for bug in Excel 2010: workbook.Open does not always return the workbook when given file name
436
431
  begin
437
432
  @ole_workbook = workbooks.Item(File.basename(filename))
438
- rescue WIN32OLERuntimeError => msg
433
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
439
434
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message}"
440
435
  end
441
- rescue WIN32OLERuntimeError => msg
436
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
442
437
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message} #{msg.backtrace}"
443
438
  end
444
439
  end
445
440
  end
446
441
 
447
- # @private
448
442
  # translating the option UpdateLinks from REO to VBA
449
443
  # setting UpdateLinks works only if calculation mode is automatic,
450
444
  # parameter 'UpdateLinks' has no effect
451
445
  def updatelinks_vba(updatelinks_reo)
452
446
  case updatelinks_reo
453
- when :alert then RobustExcelOle::XlUpdateLinksUserSetting
454
- when :never then RobustExcelOle::XlUpdateLinksNever
447
+ when :alert then RobustExcelOle::XlUpdateLinksUserSetting
448
+ when :never then RobustExcelOle::XlUpdateLinksNever
455
449
  when :always then RobustExcelOle::XlUpdateLinksAlways
456
- else RobustExcelOle::XlUpdateLinksNever
450
+ else RobustExcelOle::XlUpdateLinksNever
457
451
  end
458
452
  end
459
453
 
460
- # @private
461
454
  # workaround for linked workbooks for Excel 2007:
462
455
  # opening and closing a dummy workbook if Excel has no workbooks.
463
456
  # delay: with visible: 0.2 sec, without visible almost none
@@ -467,7 +460,7 @@ module RobustExcelOle
467
460
  workaround_condition = @excel.Version.split('.').first.to_i == 12 && workbooks.Count == 0
468
461
  if workaround_condition
469
462
  workbooks.Add
470
- @excel.calculation = options[:calculation].nil? ? @excel.calculation : options[:calculation]
463
+ @excel.calculation = options[:calculation].nil? ? @excel.properties[:calculation] : options[:calculation]
471
464
  end
472
465
  begin
473
466
  # @excel.with_displayalerts(update_links_opt == :alert ? true : @excel.displayalerts) do
@@ -521,13 +514,10 @@ module RobustExcelOle
521
514
  else
522
515
  close_workbook
523
516
  end
524
- # trace "close: canceled by user" if alive? &&
525
- # (opts[:if_unsaved] == :alert || opts[:if_unsaved] == :excel) && (not @ole_workbook.Saved)
526
517
  end
527
518
 
528
519
  private
529
520
 
530
- # @private
531
521
  def close_workbook
532
522
  @ole_workbook.Close if alive?
533
523
  @ole_workbook = nil unless alive?
@@ -545,20 +535,20 @@ module RobustExcelOle
545
535
  end
546
536
  end
547
537
 
548
- def self.for_reading(*args, &block)
549
- args = args.dup
550
- opts = args.last.is_a?(Hash) ? args.pop : {}
551
- opts = {:writable => false}.merge(opts)
552
- args.push opts
553
- unobtrusively(*args, &block)
538
+ def for_reading(opts = { }, &block)
539
+ unobtrusively({:writable => false}.merge(opts), &block)
554
540
  end
555
541
 
556
- def self.for_modifying(*args, &block)
557
- args = args.dup
558
- opts = args.last.is_a?(Hash) ? args.pop : {}
559
- opts = {:writable => true}.merge(opts)
560
- args.push opts
561
- unobtrusively(*args, &block)
542
+ def for_modifying(opts = { }, &block)
543
+ unobtrusively({:writable => true}.merge(opts), &block)
544
+ end
545
+
546
+ def self.for_reading(arg, opts = { }, &block)
547
+ unobtrusively(arg, {:writable => false}.merge(opts), &block)
548
+ end
549
+
550
+ def self.for_modifying(arg, opts = { }, &block)
551
+ unobtrusively(arg, {:writable => true}.merge(opts), &block)
562
552
  end
563
553
 
564
554
  # allows to read or modify a workbook such that its state remains unchanged
@@ -570,97 +560,64 @@ module RobustExcelOle
570
560
  # @option opts [Boolean] :writable true (default)/false changes of the workbook shall be saved/not saved
571
561
  # @option opts [Boolean] :keep_open whether the workbook shall be kept open after unobtrusively opening (default: false)
572
562
  # @return [Workbook] a workbook
573
- def self.unobtrusively(file_or_workbook, opts = { })
574
- opts = process_options(opts, :use_defaults => false)
575
- raise OptionInvalid, 'contradicting options' if opts[:writable] && opts[:read_only]
576
- opts = opts.merge({:force => {:excel => opts[:if_closed]}, :if_closed => :current,
577
- :if_unsaved => :accept, :keep_open => false})
578
- opts = opts.merge({:read_only => opts[:read_only]}) unless opts[:read_only].nil?
563
+ def self.unobtrusively(file_or_workbook, opts = { }, &block)
579
564
  file = (file_or_workbook.is_a? WIN32OLE) ? file_or_workbook.Fullname.tr('\\','/') : file_or_workbook
565
+ unobtrusively_opening(file, opts, nil, &block)
566
+ end
567
+
568
+ def unobtrusively(opts = { }, &block)
569
+ file = @stored_filename
570
+ self.class.unobtrusively_opening(file, opts, alive?, &block)
571
+ end
572
+
573
+ private
574
+
575
+ def self.unobtrusively_opening(file, opts, book_is_alive, &block)
576
+ process_options(opts)
577
+ opts = {:if_closed => :current, :keep_open => false}.merge(opts)
578
+ raise OptionInvalid, 'contradicting options' if opts[:writable] && opts[:read_only]
579
+ if book_is_alive.nil?
580
+ prefer_writable = ((!(opts[:read_only]) || opts[:writable] == true) &&
581
+ !(opts[:read_only].nil? && opts[:writable] == false))
582
+ known_book = bookstore.fetch(file, :prefer_writable => prefer_writable)
583
+ end
584
+ excel_opts = if (book_is_alive==false || (book_is_alive.nil? && (known_book.nil? || !known_book.alive?)))
585
+ {:force => {:excel => opts[:if_closed]}}
586
+ else
587
+ {:force => {:excel => opts[:force][:excel]}, :default => {:excel => opts[:default][:excel]}}
588
+ end
589
+ open_opts = excel_opts.merge({:if_unsaved => :accept})
580
590
  begin
581
- book = open(file)
591
+ open_opts[:was_open] = nil
592
+ book = open(file, open_opts)
582
593
  was_visible = book.visible
583
594
  was_writable = book.writable
584
595
  was_saved = book.saved
585
596
  was_check_compatibility = book.check_compatibility
586
- was_calculation = book.excel.calculation
587
- book.set_options(file,opts)
588
- if !was_saved && ((opts[:writable] && !was_writable) || (opts[:read_only] && was_writable))
589
- raise NotImplementedREOError, 'unsaved read-only workbook shall be written'
590
- end
597
+ was_calculation = book.excel.properties[:calculation]
598
+ book.send :apply_options, file, opts
591
599
  yield book
592
600
  ensure
593
601
  if book && book.alive?
594
- do_not_write = (opts[:read_only] || (opts[:read_only].nil? && opts[:writable] == false))
595
- book.save unless was_saved || do_not_write || book.ReadOnly
596
- # open and close if the read_only mode has changed
602
+ do_not_write = opts[:read_only] || opts[:writable]==false
603
+ book.save unless book.saved || do_not_write || !book.writable
597
604
  if (opts[:read_only] && was_writable) || (!opts[:read_only] && !was_writable)
598
- book = open(file, :read_only => !was_writable, :if_unsaved => :forget)
605
+ book.send :apply_options, file, opts.merge({:read_only => !was_writable,
606
+ :if_unsaved => (opts[:writable]==false ? :forget : :save)})
599
607
  end
600
- if book.was_open
608
+ was_open = open_opts[:was_open]
609
+ if was_open
601
610
  book.visible = was_visible
602
611
  book.CheckCompatibility = was_check_compatibility
603
612
  book.excel.calculation = was_calculation
604
613
  end
605
- book.Saved = (was_saved || !book.was_open)
606
- book.close unless book.was_open || opts[:keep_open]
607
- end
608
- end
609
- end
610
-
611
- # allows to read or modify a workbook such that its state remains unchanged
612
- # state comprises: open, saved, writable, visible, calculation mode, check compatibility
613
- # @param [String] file_or_workbook a file name or WIN32OLE workbook
614
- # @param [Hash] opts the options
615
- # @option opts [Variant] :if_closed :current (default), :new or an Excel instance
616
- # @option opts [Boolean] :read_only true/false (default), open the workbook in read-only/read-write modus (save changes)
617
- # @option opts [Boolean] :writable true (default)/false changes of the workbook shall be saved/not saved
618
- # @option opts [Boolean] :keep_open whether the workbook shall be kept open after unobtrusively opening (default: false)
619
- # @return [Workbook] a workbook
620
- =begin
621
- def self.unobtrusively(file_or_workbook, opts = { })
622
- book = new(file_or_workbook)
623
- book.unobtrusively(opts)
624
- end
625
- =end
626
-
627
- def unobtrusively(opts = { })
628
- opts = process_options(opts, :use_defaults => false)
629
- raise OptionInvalid, 'contradicting options' if opts[:writable] && opts[:read_only]
630
- opts = opts.merge({:force => {:excel => opts[:if_closed]}, :if_closed => :current,
631
- :if_unsaved => :accept, :keep_open => false})
632
- opts = opts.merge({:read_only => opts[:read_only]}) unless opts[:read_only].nil?
633
- file = stored_filename
634
- begin
635
- book = open(file, opts)
636
- was_visible = book.visible
637
- was_saved = book.saved
638
- was_writable = book.was_writable
639
- was_check_compatibility = book.CheckCompatibility
640
- was_calculation = book.excel.calculation
641
- if !was_saved && ((opts[:writable] && !was_writable) || (opts[:read_only] && was_writable))
642
- raise NotImplementedREOError, 'unsaved read-only workbook shall be written'
643
- end
644
- yield book
645
- ensure
646
- if book && book.alive?
647
- do_not_write = (opts[:read_only] || (opts[:read_only].nil? && opts[:writable] == false))
648
- book.save unless book.saved || do_not_write || book.ReadOnly
649
- # open and close if the read_only mode has changed
650
- if (opts[:read_only] && was_writable) || (!opts[:read_only] && !was_writable)
651
- book = open(file, :read_only => !was_writable, :if_unsaved => :forget)
652
- end
653
- if book.was_open
654
- book.visible = was_visible
655
- book.CheckCompatibility = was_check_compatibility
656
- book.excel.calculation = was_calculation
657
- end
658
- book.Saved = (was_saved || !book.was_open)
659
- book.close unless book.was_open || opts[:keep_open]
614
+ book.Saved = (was_saved || !was_open)
615
+ book.close unless was_open || opts[:keep_open]
660
616
  end
661
617
  end
662
618
  end
663
619
 
620
+ public
664
621
 
665
622
  # reopens a closed workbook
666
623
  # @options options
@@ -675,10 +632,6 @@ module RobustExcelOle
675
632
  def save(opts = { }) # option opts is deprecated #
676
633
  raise ObjectNotAlive, 'workbook is not alive' unless alive?
677
634
  raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
678
- # if you have open the workbook with :read_only => true,
679
- # then you could close the workbook and open it again with option :read_only => false
680
- # otherwise the workbook may already be open writable in an another Excel instance
681
- # then you could use this workbook or close the workbook there
682
635
  begin
683
636
  @ole_workbook.Save
684
637
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
@@ -708,12 +661,12 @@ module RobustExcelOle
708
661
  # :close_if_saved -> closes the blocking workbook, if it is saved,
709
662
  # otherwise raises an exception
710
663
  # @return [Workbook], the book itself, if successfully saved, raises an exception otherwise
711
- def save_as(file, opts = { })
664
+ def save_as(file, options = { })
712
665
  raise FileNameNotGiven, 'filename is nil' if file.nil?
713
666
  raise ObjectNotAlive, 'workbook is not alive' unless alive?
714
667
  raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
715
668
  raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
716
- options = self.class.process_options(opts)
669
+ self.class.process_options(options)
717
670
  if File.exist?(file)
718
671
  case options[:if_exists]
719
672
  when :overwrite
@@ -768,7 +721,6 @@ module RobustExcelOle
768
721
 
769
722
  private
770
723
 
771
- # @private
772
724
  def save_as_workbook(file, options)
773
725
  dirname, basename = File.split(file)
774
726
  file_format =
@@ -778,7 +730,7 @@ module RobustExcelOle
778
730
  when '.xlsm' then RobustExcelOle::XlOpenXMLWorkbookMacroEnabled
779
731
  end
780
732
  @ole_workbook.SaveAs(General.absolute_path(file), file_format)
781
- bookstore.store(self)
733
+ store_myself
782
734
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
783
735
  if msg.message =~ /SaveAs/ && msg.message =~ /Workbook/
784
736
  # trace "save: canceled by user" if options[:if_exists] == :alert || options[:if_exists] == :excel
@@ -788,6 +740,11 @@ module RobustExcelOle
788
740
  end
789
741
  end
790
742
 
743
+ def store_myself
744
+ bookstore.store(self)
745
+ @stored_filename = filename
746
+ end
747
+
791
748
  public
792
749
 
793
750
  # closes a given file if it is open
@@ -826,12 +783,22 @@ module RobustExcelOle
826
783
  raise NameNotFound, "could not return a sheet with name #{name.inspect}"
827
784
  end
828
785
 
786
+ def worksheets_count
787
+ @ole_workbook.Worksheets.Count
788
+ end
789
+
829
790
  def each
830
791
  @ole_workbook.Worksheets.each do |sheet|
831
792
  yield worksheet_class.new(sheet)
832
793
  end
833
794
  end
834
795
 
796
+ def worksheets
797
+ result = []
798
+ each { |worksheet| result << worksheet }
799
+ result
800
+ end
801
+
835
802
  def each_with_index(offset = 0)
836
803
  i = offset
837
804
  @ole_workbook.Worksheets.each do |sheet|
@@ -856,36 +823,18 @@ module RobustExcelOle
856
823
  new_sheet_name = opts.delete(:as)
857
824
  last_sheet_local = last_sheet
858
825
  after_or_before, base_sheet = opts.to_a.first || [:after, last_sheet_local]
826
+ base_sheet_ole = base_sheet.ole_worksheet
859
827
  begin
860
- if !JRUBY_BUG_COPYSHEETS
861
- if sheet
862
- sheet.Copy({ after_or_before.to_s => base_sheet.ole_worksheet })
863
- else
864
- @ole_workbook.Worksheets.Add({ after_or_before.to_s => base_sheet.ole_worksheet })
865
- #@ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count).Activate
866
- end
828
+ if !::COPYSHEETS_JRUBY_BUG
829
+ add_or_copy_sheet_simple(sheet, { after_or_before.to_s => base_sheet_ole })
867
830
  else
868
831
  if after_or_before == :before
869
- if sheet
870
- sheet.Copy(base_sheet.ole_worksheet)
871
- else
872
- ole_workbook.Worksheets.Add(base_sheet.ole_worksheet)
873
- end
832
+ add_or_copy_sheet_simple(sheet, base_sheet_ole)
874
833
  else
875
- #not_given = WIN32OLE_VARIANT.new(nil, WIN32OLE::VARIANT::VT_NULL)
876
- #ole_workbook.Worksheets.Add(not_given,base_sheet.ole_worksheet)
877
834
  if base_sheet.name != last_sheet_local.name
878
- if sheet
879
- sheet.Copy(base_sheet.Next)
880
- else
881
- ole_workbook.Worksheets.Add(base_sheet.Next)
882
- end
835
+ add_or_copy_sheet_simple(sheet, base_sheet.Next)
883
836
  else
884
- if sheet
885
- sheet.Copy(base_sheet.ole_worksheet)
886
- else
887
- ole_workbook.Worksheets.Add(base_sheet.ole_worksheet)
888
- end
837
+ add_or_copy_sheet_simple(sheet, base_sheet_ole)
889
838
  base_sheet.Move(ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count-1))
890
839
  ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count).Activate
891
840
  end
@@ -894,14 +843,23 @@ module RobustExcelOle
894
843
  rescue WIN32OLERuntimeError, NameNotFound, Java::OrgRacobCom::ComFailException
895
844
  raise WorksheetREOError, "could not add given worksheet #{sheet.inspect}"
896
845
  end
897
- #ole_sheet = @excel.Activesheet
898
- ole_sheet = ole_workbook.Activesheet
899
- #ole_sheet = ole_sheet.nil? ? ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count) : ole_sheet
900
- new_sheet = worksheet_class.new(ole_sheet)
846
+ new_sheet = worksheet_class.new(ole_workbook.Activesheet)
901
847
  new_sheet.name = new_sheet_name if new_sheet_name
902
848
  new_sheet
903
849
  end
904
850
 
851
+ private
852
+
853
+ def add_or_copy_sheet_simple(sheet, base_sheet_ole_or_hash)
854
+ if sheet
855
+ sheet.Copy(base_sheet_ole_or_hash)
856
+ else
857
+ ole_workbook.Worksheets.Add(base_sheet_ole_or_hash)
858
+ end
859
+ end
860
+
861
+ public
862
+
905
863
  # for compatibility to older versions
906
864
  def add_sheet(sheet = nil, opts = { })
907
865
  add_or_copy_sheet(sheet, opts)
@@ -931,30 +889,15 @@ module RobustExcelOle
931
889
  # @param [String] name the name of the range
932
890
  # @param [Variant] value the contents of the range
933
891
  def []= (name, value)
934
- old_color_if_modified = @color_if_modified
935
- workbook.color_if_modified = 42 # 42 - aqua-marin, 4-green
936
- set_namevalue_glob(name,value)
937
- workbook.color_if_modified = old_color_if_modified
892
+ set_namevalue_glob(name, value, :color => 42)
938
893
  end
939
894
 
940
895
  # sets options
941
896
  # @param [Hash] opts
942
897
  def for_this_workbook(opts)
943
898
  return unless alive?
944
- opts = self.class.process_options(opts, :use_defaults => false)
945
- visible_before = visible
946
- check_compatibility_before = check_compatibility
947
- unless opts[:read_only].nil?
948
- # if the ReadOnly status shall be changed, then close and reopen it
949
- if (!writable && !(opts[:read_only])) || (writable && opts[:read_only])
950
- opts[:check_compatibility] = check_compatibility if opts[:check_compatibility].nil?
951
- close(:if_unsaved => true)
952
- open_or_create_workbook(@stored_filename, opts)
953
- end
954
- end
955
- self.visible = opts[:force][:visible].nil? ? visible_before : opts[:force][:visible]
956
- self.CheckCompatibility = opts[:check_compatibility].nil? ? check_compatibility_before : opts[:check_compatibility]
957
- @excel.calculation = opts[:calculation] unless opts[:calculation].nil?
899
+ self.class.process_options(opts, :use_defaults => false)
900
+ self.send :apply_options, @stored_filename, opts
958
901
  end
959
902
 
960
903
  # brings workbook to foreground, makes it available for heyboard inputs, makes the Excel instance visible
@@ -970,7 +913,6 @@ module RobustExcelOle
970
913
  true
971
914
  rescue
972
915
  @ole_workbook = nil # dead object won't be alive again
973
- # t $!.message
974
916
  false
975
917
  end
976
918
 
@@ -990,7 +932,7 @@ module RobustExcelOle
990
932
  end
991
933
 
992
934
  def calculation
993
- @excel.calculation if @ole_workbook
935
+ @excel.properties[:calculation] if @ole_workbook
994
936
  end
995
937
 
996
938
  # @private
@@ -1000,7 +942,7 @@ module RobustExcelOle
1000
942
 
1001
943
  # returns true, if the workbook is visible, false otherwise
1002
944
  def visible
1003
- @excel.visible && @ole_workbook.Windows(@ole_workbook.Name).Visible
945
+ @excel.Visible && @ole_workbook.Windows(@ole_workbook.Name).Visible
1004
946
  end
1005
947
 
1006
948
  # makes both the Excel instance and the window of the workbook visible, or the window invisible
@@ -1032,6 +974,7 @@ module RobustExcelOle
1032
974
  self.filename == other_book.filename
1033
975
  end
1034
976
 
977
+ # @private
1035
978
  def self.books
1036
979
  bookstore.books
1037
980
  end
@@ -1046,6 +989,11 @@ module RobustExcelOle
1046
989
  self.class.bookstore
1047
990
  end
1048
991
 
992
+ # @private
993
+ def workbook
994
+ self
995
+ end
996
+
1049
997
  # @private
1050
998
  def to_s
1051
999
  self.filename.to_s
@@ -1077,16 +1025,6 @@ module RobustExcelOle
1077
1025
  end
1078
1026
  end
1079
1027
 
1080
- # @private
1081
- def self.address_class
1082
- @address_class ||= begin
1083
- module_name = self.parent_name
1084
- "#{module_name}::Address".constantize
1085
- rescue NameError => e
1086
- Address
1087
- end
1088
- end
1089
-
1090
1028
  # @private
1091
1029
  def excel_class
1092
1030
  self.class.excel_class
@@ -1097,11 +1035,6 @@ module RobustExcelOle
1097
1035
  self.class.worksheet_class
1098
1036
  end
1099
1037
 
1100
- # @private
1101
- def address_class
1102
- self.class.address_class
1103
- end
1104
-
1105
1038
  include MethodHelpers
1106
1039
 
1107
1040
  private
@@ -1109,7 +1042,7 @@ module RobustExcelOle
1109
1042
  def method_missing(name, *args)
1110
1043
  if name.to_s[0,1] =~ /[A-Z]/
1111
1044
  raise ObjectNotAlive, 'method missing: workbook not alive' unless alive?
1112
- if JRUBY_BUG_ERRORMESSAGE
1045
+ if ::ERRORMESSAGE_JRUBY_BUG
1113
1046
  begin
1114
1047
  @ole_workbook.send(name, *args)
1115
1048
  rescue Java::OrgRacobCom::ComFailException
@@ -1131,6 +1064,47 @@ module RobustExcelOle
1131
1064
 
1132
1065
  public
1133
1066
 
1067
+ # @private
1068
+ class WorkbookBlocked < WorkbookREOError
1069
+ end
1070
+
1071
+ # @private
1072
+ class WorkbookNotSaved < WorkbookREOError
1073
+ end
1074
+
1075
+ # @private
1076
+ class WorkbookReadOnly < WorkbookREOError
1077
+ end
1078
+
1079
+ # @private
1080
+ class WorkbookBeingUsed < WorkbookREOError
1081
+ end
1082
+
1083
+ # @private
1084
+ class WorkbookConnectingUnsavedError < WorkbookREOError
1085
+ end
1086
+
1087
+ # @private
1088
+ class WorkbookConnectingBlockingError < WorkbookREOError
1089
+ end
1090
+
1091
+ # @private
1092
+ class WorkbookConnectingUnknownError < WorkbookREOError
1093
+ end
1094
+
1095
+ # @private
1096
+ class FileAlreadyExists < FileREOError
1097
+ end
1098
+
1099
+ # @private
1100
+ class FileNameNotGiven < FileREOError
1101
+ end
1102
+
1103
+ # @private
1104
+ class FileNotFound < FileREOError
1105
+ end
1106
+
1107
+
1134
1108
  Book = Workbook
1135
1109
 
1136
1110
  end