robust_excel_ole 1.13 → 1.18

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +47 -4
  3. data/README.rdoc +52 -47
  4. data/___dummy_workbook.xls +0 -0
  5. data/docs/README_excel.rdoc +9 -3
  6. data/docs/README_open.rdoc +86 -44
  7. data/docs/README_ranges.rdoc +90 -81
  8. data/docs/README_save_close.rdoc +9 -9
  9. data/docs/README_sheet.rdoc +17 -17
  10. data/examples/example_ruby_library.rb +27 -0
  11. data/extconf.rb +7 -0
  12. data/lib/robust_excel_ole.rb +7 -4
  13. data/lib/robust_excel_ole/{address.rb → address_tool.rb} +23 -22
  14. data/lib/robust_excel_ole/{reo_common.rb → base.rb} +3 -92
  15. data/lib/robust_excel_ole/bookstore.rb +14 -77
  16. data/lib/robust_excel_ole/cell.rb +30 -18
  17. data/lib/robust_excel_ole/excel.rb +138 -92
  18. data/lib/robust_excel_ole/general.rb +40 -15
  19. data/lib/robust_excel_ole/range.rb +42 -19
  20. data/lib/robust_excel_ole/range_owners.rb +40 -25
  21. data/lib/robust_excel_ole/vba_objects.rb +30 -0
  22. data/lib/robust_excel_ole/version.rb +1 -1
  23. data/lib/robust_excel_ole/workbook.rb +320 -319
  24. data/lib/robust_excel_ole/worksheet.rb +51 -25
  25. data/robust_excel_ole.gemspec +1 -0
  26. data/spec/address_tool_spec.rb +175 -0
  27. data/spec/{reo_common_spec.rb → base_spec.rb} +19 -34
  28. data/spec/bookstore_spec.rb +3 -3
  29. data/spec/cell_spec.rb +67 -25
  30. data/spec/data/more_data/workbook.xls +0 -0
  31. data/spec/excel_spec.rb +137 -369
  32. data/spec/general_spec.rb +21 -27
  33. data/spec/range_spec.rb +57 -3
  34. data/spec/workbook_spec.rb +11 -79
  35. data/spec/workbook_specs/workbook_misc_spec.rb +29 -40
  36. data/spec/workbook_specs/workbook_open_spec.rb +599 -31
  37. data/spec/workbook_specs/workbook_unobtr_spec.rb +760 -93
  38. data/spec/worksheet_spec.rb +36 -4
  39. metadata +12 -7
  40. 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.13"
2
+ VERSION = "1.18"
3
3
  end
@@ -11,35 +11,38 @@ module RobustExcelOle
11
11
 
12
12
  class Workbook < RangeOwners
13
13
 
14
- attr_accessor :excel
15
- attr_accessor :ole_workbook
16
- attr_accessor :stored_filename
17
- attr_accessor :color_if_modified
18
- attr_reader :workbook
14
+ #include General
15
+
16
+ attr_reader :ole_workbook
17
+ attr_reader :excel
18
+ attr_reader :stored_filename
19
19
 
20
20
  alias ole_object ole_workbook
21
21
 
22
- DEFAULT_OPEN_OPTS = {
23
- :default => {:excel => :current},
22
+ CORE_DEFAULT_OPEN_OPTS = {
23
+ :default => {:excel => :current},
24
24
  :force => {},
25
- :update_links => :never,
25
+ :update_links => :never
26
+ }.freeze
27
+
28
+ DEFAULT_OPEN_OPTS = {
26
29
  :if_unsaved => :raise,
27
30
  :if_obstructed => :raise,
28
31
  :if_absent => :raise,
29
- :if_exists => :raise,
30
- :check_compatibility => false
31
- }.freeze
32
-
33
- CORE_DEFAULT_OPEN_OPTS = {
34
- :default => {:excel => :current}, :force => {}, :update_links => :never
35
- }.freeze
32
+ :if_exists => :raise
33
+ }.merge(CORE_DEFAULT_OPEN_OPTS).freeze
36
34
 
37
- ABBREVIATIONS = [[:default,:d], [:force, :f], [:excel, :e], [:visible, :v],
38
- [: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
39
42
 
40
43
 
41
44
  # opens a workbook.
42
- # @param [String] file the file name
45
+ # @param [String] file_or_workbook a file name or WIN32OLE workbook
43
46
  # @param [Hash] opts the options
44
47
  # @option opts [Hash] :default or :d
45
48
  # @option opts [Hash] :force or :f
@@ -81,49 +84,59 @@ module RobustExcelOle
81
84
  # :visible true -> makes the workbook visible
82
85
  # :check_compatibility true -> check compatibility when saving
83
86
  # :update_links true -> user is being asked how to update links, false -> links are never updated
84
- # @return [Workbook] a representation of a workbook
85
- def self.new(file_or_workbook, opts = { }, &block)
86
- options = process_options(opts)
87
- 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
88
94
  file = file_or_workbook.Fullname.tr('\\','/')
89
- else
95
+ when Workbook
96
+ file = file_or_workbook.Fullname.tr('\\','/')
97
+ when String
90
98
  file = file_or_workbook
91
- raise(FileNameNotGiven, 'filename is nil') if file.nil?
92
- 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'
93
102
  end
94
103
  # try to fetch the workbook from the bookstore
104
+ set_was_open opts, file_or_workbook.is_a?(WIN32OLE)
95
105
  book = nil
96
- if options[:force][:excel] != :new
106
+ if opts[:force][:excel] != :new
97
107
  # if readonly is true, then prefer a book that is given in force_excel if this option is set
98
- forced_excel =
99
- (options[:force][:excel].nil? || options[:force][:excel] == :current) ?
100
- (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
101
114
  begin
102
115
  book = if File.exists?(file)
103
- bookstore.fetch(file, :prefer_writable => !(options[:read_only]),
104
- :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))
105
118
  end
106
119
  rescue
107
- trace "#{$!.message}"
120
+ raise
121
+ #trace "#{$!.message}"
108
122
  end
109
- if book
123
+ if book
124
+ set_was_open opts, book.alive?
110
125
  # drop the fetched workbook if it shall be opened in another Excel instance
111
126
  # or the workbook is an unsaved workbook that should not be accepted
112
- if (options[:force][:excel].nil? || options[:force][:excel] == :current || forced_excel == book.excel) &&
113
- !(book.alive? && !book.saved && (options[:if_unsaved] != :accept))
114
- options[:force][:excel] = book.excel if book.excel && book.excel.alive?
115
- book.ensure_workbook(file,options)
116
- 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
117
132
  return book
118
133
  end
119
134
  end
120
135
  end
121
- super(file_or_workbook, options, &block)
136
+ super(file_or_workbook, opts)
122
137
  end
123
138
 
124
- def self.open(file_or_workbook, opts = { }, &block)
125
- new(file_or_workbook, opts, &block)
126
- end
139
+ singleton_class.send :alias_method, :open, :new
127
140
 
128
141
  # creates a new Workbook object, if a file name is given
129
142
  # Promotes the win32ole workbook to a Workbook object, if a win32ole-workbook is given
@@ -131,26 +144,23 @@ module RobustExcelOle
131
144
  # @param [Hash] opts
132
145
  # @option opts [Symbol] see above
133
146
  # @return [Workbook] a workbook
134
- def initialize(file_or_workbook, options = { }, &block)
147
+ def initialize(file_or_workbook, opts)
135
148
  if file_or_workbook.is_a? WIN32OLE
136
149
  @ole_workbook = file_or_workbook
137
- ole_excel = begin
138
- WIN32OLE.connect(@ole_workbook.Fullname).Application
139
- rescue
150
+ ole_excel = begin
151
+ @ole_workbook.Application
152
+ rescue WIN32OLERuntimeError
140
153
  raise ExcelREOError, 'could not determine the Excel instance'
141
154
  end
142
155
  @excel = excel_class.new(ole_excel)
143
- filename = file_or_workbook.Fullname.tr('\\','/')
156
+ filename = @ole_workbook.Fullname.tr('\\','/')
144
157
  else
145
158
  filename = file_or_workbook
146
- ensure_workbook(filename, options)
159
+ ensure_workbook(filename, opts)
147
160
  end
148
- set_options(filename, options)
149
- bookstore.store(self)
150
- @workbook = @excel.workbook = self
151
- r1c1_letters = @ole_workbook.Worksheets.Item(1).Cells.Item(1,1).Address(true,true,XlR1C1).gsub(/[0-9]/,'') #('ReferenceStyle' => XlR1C1).gsub(/[0-9]/,'')
152
- address_class.new(r1c1_letters)
153
- if block
161
+ apply_options(filename, opts)
162
+ store_myself
163
+ if block_given?
154
164
  begin
155
165
  yield self
156
166
  ensure
@@ -161,52 +171,46 @@ module RobustExcelOle
161
171
 
162
172
  private
163
173
 
164
- # translates abbreviations and synonyms and merges with default options
165
- def self.process_options(options, proc_opts = {:use_defaults => true})
166
- translator = proc do |opts|
167
- erg = {}
168
- opts.each do |key,value|
169
- new_key = key
170
- ABBREVIATIONS.each { |long,short| new_key = long if key == short }
171
- if value.is_a?(Hash)
172
- erg[new_key] = {}
173
- value.each do |k,v|
174
- new_k = k
175
- ABBREVIATIONS.each { |l,s| new_k = l if k == s }
176
- erg[new_key][new_k] = v
177
- end
178
- else
179
- erg[new_key] = value
180
- end
181
- end
182
- erg[:default] ||= {}
183
- erg[:force] ||= {}
184
- force_list = [:visible, :excel]
185
- erg.each { |key,value| erg[:force][key] = value if force_list.include?(key) }
186
- erg[:default][:excel] = erg[:default_excel] unless erg[:default_excel].nil?
187
- erg[:force][:excel] = erg[:force_excel] unless erg[:force_excel].nil?
188
- erg[:default][:excel] = :current if erg[:default][:excel] == :reuse || erg[:default][:excel] == :active
189
- erg[:force][:excel] = :current if erg[:force][:excel] == :reuse || erg[:force][:excel] == :active
190
- erg
191
- end
192
- opts = translator.call(options)
193
- default_open_opts = proc_opts[:use_defaults] ? DEFAULT_OPEN_OPTS : CORE_DEFAULT_OPEN_OPTS
194
- default_opts = translator.call(default_open_opts)
195
- opts = default_opts.merge(opts)
196
- opts[:default] = default_opts[:default].merge(opts[:default]) unless opts[:default].nil?
197
- opts[:force] = default_opts[:force].merge(opts[:force]) unless opts[:force].nil?
198
- opts
174
+ def self.set_was_open(hash, value)
175
+ hash[:was_open] = value if hash.has_key?(:was_open)
199
176
  end
200
177
 
201
- # returns an Excel object when given Excel, Workbook or Win32ole object representing a Workbook or an Excel
202
- # @private
203
- def self.excel_of(object)
204
- begin
205
- object = object.to_reo if object.is_a? WIN32OLE
206
- object.excel
207
- rescue
208
- 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
209
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
210
214
  end
211
215
 
212
216
  public
@@ -215,102 +219,91 @@ module RobustExcelOle
215
219
  # ensures an excel but not for jruby if current Excel shall be used
216
220
  def ensure_excel(options)
217
221
  return if @excel && @excel.alive?
218
- excel_option = options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
222
+ excel_option = options[:force][:excel] || options[:default][:excel] || :current
219
223
  @excel = if excel_option == :new
220
224
  excel_class.new(:reuse => false)
221
- elsif excel_option.nil? || excel_option == :current
225
+ elsif excel_option == :current
222
226
  excel_class.new(:reuse => true)
227
+ elsif excel_option.respond_to?(:to_reo)
228
+ excel_option.to_reo.excel
223
229
  else
224
- self.class.excel_of(excel_option)
230
+ raise TypeREOError, "provided Excel option value is neither an Excel object nor a valid option"
225
231
  end
226
- raise ExcelREOError, "excel is not alive" unless @excel && @excel.alive?
232
+ raise ExcelREOError, "Excel is not alive" unless @excel && @excel.alive?
227
233
  end
228
234
 
229
- # @private
230
- # restriction for jruby: does not manage conflicts with blocking or unsaved workbooks
235
+
236
+ # @private
231
237
  def ensure_workbook(filename, options)
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
232
241
  if options[:if_unsaved]==:accept &&
233
242
  ((options[:read_only]==true && self.ReadOnly==false) || (options[:read_only]==false && self.ReadOnly==true))
234
243
  raise OptionInvalid, ":if_unsaved:accept and change of read-only mode is not possible"
235
244
  end
236
- unless @ole_workbook && alive?
237
- filename = @stored_filename ? @stored_filename : filename
238
- manage_nonexisting_file(filename,options)
239
- excel_option = options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
240
- if JRUBY_BUG_CONNECT
241
- (excel_option.nil? || excel_option == :current) && filename[0] != '/'
242
- begin
243
- connect(filename,options)
244
- rescue WorkbookConnectingUnsavedError
245
- raise WorkbookNotSaved, "workbook is already open but not saved: #{File.basename(filename).inspect}"
246
- rescue WorkbookConnectingBlockingError
247
- raise WorkbookBlocked, "can't open workbook #{filename}"+
248
- "\nbecause it is being blocked by a workbook with the same name in a different path."
249
- rescue WorkbookConnectingUnknownError
250
- raise WorkbookREOError, "can't connect to workbook #{filename}"
251
- end
252
- manage_unsaved_workbook(filename,options) unless @ole_workbook.Saved
253
- else
254
- ensure_excel(options)
255
- workbooks = @excel.Workbooks
256
- @ole_workbook = workbooks.Item(File.basename(filename)) rescue nil if @ole_workbook.nil?
257
- if @ole_workbook
258
- manage_blocking_or_unsaved_workbook(filename,options)
259
- else
260
- if excel_option.nil? || excel_option == :current &&
261
- (!JRUBY_BUG_CONNECT || filename[0] != '/')
262
- connect(filename,options)
263
- else
264
- open_or_create_workbook(filename,options)
265
- end
266
- 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)
267
261
  end
268
- end
262
+ end
269
263
  end
270
264
 
271
- # @private
272
- 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
273
271
  if (!options[:read_only].nil?) && options[:read_only] != @ole_workbook.ReadOnly
274
- @excel.with_displayalerts(false) { @ole_workbook.Close }
275
- @ole_workbook = nil
276
- open_or_create_workbook(filename, options)
272
+ ensure_workbook(filename, options)
277
273
  end
278
274
  retain_saved do
279
275
  self.visible = options[:force][:visible].nil? ? @excel.Visible : options[:force][:visible]
280
276
  @excel.calculation = options[:calculation] unless options[:calculation].nil?
281
277
  @ole_workbook.CheckCompatibility = options[:check_compatibility] unless options[:check_compatibility].nil?
282
- end
278
+ end
283
279
  end
284
280
 
285
- private
286
-
287
- # @private
288
- # connects to an unknown workbook and returns true, if it exists
289
- def connect(filename,options)
290
- abs_filename = General.absolute_path(filename)
281
+ # connects to an unknown workbook
282
+ def connect(filename, options)
283
+ workbooks_number = excel_class.excels_number==0 ? 0 : excel_class.current.Workbooks.Count
291
284
  @ole_workbook = begin
292
- WIN32OLE.connect(abs_filename)
285
+ WIN32OLE.connect(General.absolute_path(filename))
293
286
  rescue
294
287
  if $!.message =~ /moniker/
295
- raise WorkbookConnectingBlockingError
296
- else
297
- raise WorkbookConnectingUnknownError
288
+ raise WorkbookConnectingBlockingError, "some workbook is blocking when connecting"
289
+ else
290
+ raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook"
298
291
  end
299
292
  end
300
293
  ole_excel = begin
301
294
  @ole_workbook.Application
302
295
  rescue
303
296
  if $!.message =~ /dispid/
304
- raise WorkbookConnectingUnsavedError
305
- else
306
- raise WorkbookConnectingUnknownError
297
+ raise WorkbookConnectingUnsavedError, "workbook is unsaved when connecting"
298
+ else
299
+ raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook"
307
300
  end
308
301
  end
302
+ set_was_open options, (ole_excel.Workbooks.Count == workbooks_number)
309
303
  @excel = excel_class.new(ole_excel)
310
304
  end
311
305
 
312
- # @private
313
- def manage_nonexisting_file(filename,options)
306
+ def manage_nonexisting_file(filename, options)
314
307
  return if File.exist?(filename)
315
308
  abs_filename = General.absolute_path(filename)
316
309
  if options[:if_absent] == :create
@@ -328,11 +321,12 @@ module RobustExcelOle
328
321
  end
329
322
  end
330
323
 
331
- # @private
332
- def manage_blocking_or_unsaved_workbook(filename,options)
333
- obstructed_by_other_book = if (File.basename(filename) == File.basename(@ole_workbook.Fullname))
334
- General.absolute_path(filename) != @ole_workbook.Fullname
335
- 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))
336
330
  if obstructed_by_other_book
337
331
  # workbook is being obstructed by a workbook with same name and different path
338
332
  manage_blocking_workbook(filename,options)
@@ -344,8 +338,7 @@ module RobustExcelOle
344
338
  end
345
339
  end
346
340
 
347
- # @private
348
- def manage_blocking_workbook(filename,options)
341
+ def manage_blocking_workbook(filename, options)
349
342
  case options[:if_obstructed]
350
343
  when :raise
351
344
  raise WorkbookBlocked, "can't open workbook #{filename},
@@ -359,7 +352,8 @@ module RobustExcelOle
359
352
  manage_saving_workbook(filename, options)
360
353
  when :close_if_saved
361
354
  if !@ole_workbook.Saved
362
- 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"
363
357
  else
364
358
  manage_forgetting_workbook(filename, options)
365
359
  end
@@ -371,15 +365,14 @@ module RobustExcelOle
371
365
  end
372
366
  end
373
367
 
374
- # @private
375
- def manage_unsaved_workbook(filename,options)
368
+ def manage_unsaved_workbook(filename, options)
376
369
  case options[:if_unsaved]
377
370
  when :raise
378
371
  raise WorkbookNotSaved, "workbook is already open but not saved: #{File.basename(filename).inspect}" +
379
372
  "\nHint: Save the workbook or open the workbook using option :if_unsaved with values :forget and :accept to
380
373
  close the unsaved workbook and reopen it, or to let the unsaved workbook open, respectively"
381
374
  when :forget
382
- manage_forgetting_workbook(filename,options)
375
+ manage_forgetting_workbook(filename, options)
383
376
  when :accept
384
377
  # do nothing
385
378
  when :save
@@ -394,29 +387,26 @@ module RobustExcelOle
394
387
  end
395
388
  end
396
389
 
397
- # @private
398
390
  def manage_forgetting_workbook(filename, options)
399
391
  @excel.with_displayalerts(false) { @ole_workbook.Close }
400
392
  @ole_workbook = nil
401
393
  open_or_create_workbook(filename, options)
402
394
  end
403
395
 
404
- # @private
405
396
  def manage_saving_workbook(filename, options)
406
397
  save unless @ole_workbook.Saved
407
398
  manage_forgetting_workbook(filename, options)
408
399
  end
409
400
 
410
- # @private
411
401
  def manage_new_excel(filename, options)
412
402
  @excel = excel_class.new(:reuse => false)
413
403
  @ole_workbook = nil
414
404
  open_or_create_workbook(filename, options)
415
405
  end
416
406
 
417
- # @private
418
- def open_or_create_workbook(filename, options)
419
- 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 )
420
410
  begin
421
411
  abs_filename = General.absolute_path(filename)
422
412
  begin
@@ -431,7 +421,7 @@ module RobustExcelOle
431
421
  updatelinks_vba(options[:update_links]),
432
422
  options[:read_only] )
433
423
  end
434
- rescue WIN32OLERuntimeError => msg
424
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
435
425
  # for Excel2007: for option :if_unsaved => :alert and user cancels: this error appears?
436
426
  # if yes: distinguish these events
437
427
  raise UnexpectedREOError, "cannot open workbook: #{msg.message} #{msg.backtrace}"
@@ -440,29 +430,27 @@ module RobustExcelOle
440
430
  # workaround for bug in Excel 2010: workbook.Open does not always return the workbook when given file name
441
431
  begin
442
432
  @ole_workbook = workbooks.Item(File.basename(filename))
443
- rescue WIN32OLERuntimeError => msg
433
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
444
434
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message}"
445
435
  end
446
- rescue WIN32OLERuntimeError => msg
436
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
447
437
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message} #{msg.backtrace}"
448
438
  end
449
439
  end
450
440
  end
451
441
 
452
- # @private
453
442
  # translating the option UpdateLinks from REO to VBA
454
443
  # setting UpdateLinks works only if calculation mode is automatic,
455
444
  # parameter 'UpdateLinks' has no effect
456
445
  def updatelinks_vba(updatelinks_reo)
457
446
  case updatelinks_reo
458
- when :alert then RobustExcelOle::XlUpdateLinksUserSetting
459
- when :never then RobustExcelOle::XlUpdateLinksNever
447
+ when :alert then RobustExcelOle::XlUpdateLinksUserSetting
448
+ when :never then RobustExcelOle::XlUpdateLinksNever
460
449
  when :always then RobustExcelOle::XlUpdateLinksAlways
461
- else RobustExcelOle::XlUpdateLinksNever
450
+ else RobustExcelOle::XlUpdateLinksNever
462
451
  end
463
452
  end
464
453
 
465
- # @private
466
454
  # workaround for linked workbooks for Excel 2007:
467
455
  # opening and closing a dummy workbook if Excel has no workbooks.
468
456
  # delay: with visible: 0.2 sec, without visible almost none
@@ -472,7 +460,7 @@ module RobustExcelOle
472
460
  workaround_condition = @excel.Version.split('.').first.to_i == 12 && workbooks.Count == 0
473
461
  if workaround_condition
474
462
  workbooks.Add
475
- @excel.calculation = options[:calculation].nil? ? @excel.calculation : options[:calculation]
463
+ @excel.calculation = options[:calculation].nil? ? @excel.properties[:calculation] : options[:calculation]
476
464
  end
477
465
  begin
478
466
  # @excel.with_displayalerts(update_links_opt == :alert ? true : @excel.displayalerts) do
@@ -526,13 +514,10 @@ module RobustExcelOle
526
514
  else
527
515
  close_workbook
528
516
  end
529
- # trace "close: canceled by user" if alive? &&
530
- # (opts[:if_unsaved] == :alert || opts[:if_unsaved] == :excel) && (not @ole_workbook.Saved)
531
517
  end
532
518
 
533
519
  private
534
520
 
535
- # @private
536
521
  def close_workbook
537
522
  @ole_workbook.Close if alive?
538
523
  @ole_workbook = nil unless alive?
@@ -550,84 +535,81 @@ module RobustExcelOle
550
535
  end
551
536
  end
552
537
 
553
- def self.for_reading(*args, &block)
554
- args = args.dup
555
- opts = args.last.is_a?(Hash) ? args.pop : {}
556
- opts = {:writable => false}.merge(opts)
557
- args.push opts
558
- unobtrusively(*args, &block)
538
+ def for_reading(opts = { }, &block)
539
+ unobtrusively({:writable => false}.merge(opts), &block)
559
540
  end
560
541
 
561
- def self.for_modifying(*args, &block)
562
- args = args.dup
563
- opts = args.last.is_a?(Hash) ? args.pop : {}
564
- opts = {:writable => true}.merge(opts)
565
- args.push opts
566
- unobtrusively(*args, &block)
542
+ def for_modifying(opts = { }, &block)
543
+ unobtrusively({:writable => true}.merge(opts), &block)
567
544
  end
568
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)
552
+ end
569
553
 
570
554
  # allows to read or modify a workbook such that its state remains unchanged
571
555
  # state comprises: open, saved, writable, visible, calculation mode, check compatibility
572
- # @param [String] file the file name
556
+ # @param [String] file_or_workbook a file name or WIN32OLE workbook
573
557
  # @param [Hash] opts the options
574
558
  # @option opts [Variant] :if_closed :current (default), :new or an Excel instance
575
- # @option opts [Boolean] :read_only true/false, open the workbook in read-only/read-write modus (save changes)
576
- # @option opts [Boolean] :writable true/false changes of the workbook shall be saved/not saved
577
- # @option opts [Boolean] :rw_change_excel Excel instance in which the workbook with the new
578
- # write permissions shall be opened :current (default), :new or an Excel instance
579
- # @option opts [Boolean] :keep_open whether the workbook shall be kept open after unobtrusively opening
559
+ # @option opts [Boolean] :read_only true/false (default), open the workbook in read-only/read-write modus (save changes)
560
+ # @option opts [Boolean] :writable true (default)/false changes of the workbook shall be saved/not saved
561
+ # @option opts [Boolean] :keep_open whether the workbook shall be kept open after unobtrusively opening (default: false)
580
562
  # @return [Workbook] a workbook
581
- def self.unobtrusively(file, opts = { })
582
- opts = process_options(opts, :use_defaults => false)
583
- opts = {:if_closed => :current,
584
- :rw_change_excel => :current,
585
- :keep_open => false}.merge(opts)
586
- raise OptionInvalid, 'contradicting options' if opts[:writable] && opts[:read_only]
587
- prefer_writable = ((!(opts[:read_only]) || opts[:writable] == true) &&
588
- !(opts[:read_only].nil? && opts[:writable] == false))
589
- do_not_write = (opts[:read_only] || (opts[:read_only].nil? && opts[:writable] == false))
590
- book = bookstore.fetch(file, :prefer_writable => prefer_writable)
591
- was_open = book && book.alive?
592
- if was_open
593
- was_saved = book.saved
594
- was_writable = book.writable
595
- was_visible = book.visible
596
- was_calculation = book.calculation
597
- was_check_compatibility = book.check_compatibility
598
- if (opts[:writable] && !was_writable && !was_saved) ||
599
- (opts[:read_only] && was_writable && !was_saved)
600
- raise NotImplementedREOError, 'unsaved read-only workbook shall be written'
601
- end
602
- opts[:rw_change_excel] = book.excel if opts[:rw_change_excel] == :current
563
+ def self.unobtrusively(file_or_workbook, opts = { }, &block)
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]}}
603
588
  end
604
- change_rw_mode = ((opts[:read_only] && was_writable) || (opts[:writable] && !was_writable))
589
+ open_opts = excel_opts.merge({:if_unsaved => :accept})
605
590
  begin
606
- book =
607
- if was_open
608
- if change_rw_mode
609
- opts = opts.merge({:force => {:excel => opts[:rw_change_excel]}, :read_only => do_not_write})
610
- open(file, opts)
611
- else
612
- book
613
- end
614
- else
615
- opts = opts.merge({:force => {:excel => opts[:if_closed]}, :read_only => do_not_write})
616
- open(file, opts)
617
- end
591
+ open_opts[:was_open] = nil
592
+ book = open(file, open_opts)
593
+ was_visible = book.visible
594
+ was_writable = book.writable
595
+ was_saved = book.saved
596
+ was_check_compatibility = book.check_compatibility
597
+ was_calculation = book.excel.properties[:calculation]
598
+ book.send :apply_options, file, opts
618
599
  yield book
619
600
  ensure
620
601
  if book && book.alive?
621
- book.save unless book.saved || do_not_write || book.ReadOnly
602
+ do_not_write = opts[:read_only] || opts[:writable]==false
603
+ book.save unless book.saved || do_not_write || !book.writable
604
+ if (opts[:read_only] && was_writable) || (!opts[:read_only] && !was_writable)
605
+ book.send :apply_options, file, opts.merge({:read_only => !was_writable,
606
+ :if_unsaved => (opts[:writable]==false ? :forget : :save)})
607
+ end
608
+ was_open = open_opts[:was_open]
622
609
  if was_open
623
- if opts[:rw_change_excel] == book.excel && change_rw_mode
624
- book.close
625
- opts = opts.merge({:force => {:excel => opts[:rw_change_excel]}, :read_only => !was_writable})
626
- book = open(file, opts)
627
- end
628
- book.excel.calculation = was_calculation
610
+ book.visible = was_visible
629
611
  book.CheckCompatibility = was_check_compatibility
630
- # book.visible = was_visible # not necessary
612
+ book.excel.calculation = was_calculation
631
613
  end
632
614
  book.Saved = (was_saved || !was_open)
633
615
  book.close unless was_open || opts[:keep_open]
@@ -635,6 +617,8 @@ module RobustExcelOle
635
617
  end
636
618
  end
637
619
 
620
+ public
621
+
638
622
  # reopens a closed workbook
639
623
  # @options options
640
624
  def reopen(options = { })
@@ -648,10 +632,6 @@ module RobustExcelOle
648
632
  def save(opts = { }) # option opts is deprecated #
649
633
  raise ObjectNotAlive, 'workbook is not alive' unless alive?
650
634
  raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
651
- # if you have open the workbook with :read_only => true,
652
- # then you could close the workbook and open it again with option :read_only => false
653
- # otherwise the workbook may already be open writable in an another Excel instance
654
- # then you could use this workbook or close the workbook there
655
635
  begin
656
636
  @ole_workbook.Save
657
637
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
@@ -681,12 +661,12 @@ module RobustExcelOle
681
661
  # :close_if_saved -> closes the blocking workbook, if it is saved,
682
662
  # otherwise raises an exception
683
663
  # @return [Workbook], the book itself, if successfully saved, raises an exception otherwise
684
- def save_as(file, opts = { })
664
+ def save_as(file, options = { })
685
665
  raise FileNameNotGiven, 'filename is nil' if file.nil?
686
666
  raise ObjectNotAlive, 'workbook is not alive' unless alive?
687
667
  raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
688
668
  raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
689
- options = self.class.process_options(opts)
669
+ self.class.process_options(options)
690
670
  if File.exist?(file)
691
671
  case options[:if_exists]
692
672
  when :overwrite
@@ -741,7 +721,6 @@ module RobustExcelOle
741
721
 
742
722
  private
743
723
 
744
- # @private
745
724
  def save_as_workbook(file, options)
746
725
  dirname, basename = File.split(file)
747
726
  file_format =
@@ -751,7 +730,7 @@ module RobustExcelOle
751
730
  when '.xlsm' then RobustExcelOle::XlOpenXMLWorkbookMacroEnabled
752
731
  end
753
732
  @ole_workbook.SaveAs(General.absolute_path(file), file_format)
754
- bookstore.store(self)
733
+ store_myself
755
734
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
756
735
  if msg.message =~ /SaveAs/ && msg.message =~ /Workbook/
757
736
  # trace "save: canceled by user" if options[:if_exists] == :alert || options[:if_exists] == :excel
@@ -761,6 +740,11 @@ module RobustExcelOle
761
740
  end
762
741
  end
763
742
 
743
+ def store_myself
744
+ bookstore.store(self)
745
+ @stored_filename = filename
746
+ end
747
+
764
748
  public
765
749
 
766
750
  # closes a given file if it is open
@@ -799,12 +783,22 @@ module RobustExcelOle
799
783
  raise NameNotFound, "could not return a sheet with name #{name.inspect}"
800
784
  end
801
785
 
786
+ def worksheets_count
787
+ @ole_workbook.Worksheets.Count
788
+ end
789
+
802
790
  def each
803
791
  @ole_workbook.Worksheets.each do |sheet|
804
792
  yield worksheet_class.new(sheet)
805
793
  end
806
794
  end
807
795
 
796
+ def worksheets
797
+ result = []
798
+ each { |worksheet| result << worksheet }
799
+ result
800
+ end
801
+
808
802
  def each_with_index(offset = 0)
809
803
  i = offset
810
804
  @ole_workbook.Worksheets.each do |sheet|
@@ -829,36 +823,18 @@ module RobustExcelOle
829
823
  new_sheet_name = opts.delete(:as)
830
824
  last_sheet_local = last_sheet
831
825
  after_or_before, base_sheet = opts.to_a.first || [:after, last_sheet_local]
826
+ base_sheet_ole = base_sheet.ole_worksheet
832
827
  begin
833
- if !JRUBY_BUG_COPYSHEETS
834
- if sheet
835
- sheet.Copy({ after_or_before.to_s => base_sheet.ole_worksheet })
836
- else
837
- @ole_workbook.Worksheets.Add({ after_or_before.to_s => base_sheet.ole_worksheet })
838
- #@ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count).Activate
839
- end
828
+ if !::COPYSHEETS_JRUBY_BUG
829
+ add_or_copy_sheet_simple(sheet, { after_or_before.to_s => base_sheet_ole })
840
830
  else
841
831
  if after_or_before == :before
842
- if sheet
843
- sheet.Copy(base_sheet.ole_worksheet)
844
- else
845
- ole_workbook.Worksheets.Add(base_sheet.ole_worksheet)
846
- end
832
+ add_or_copy_sheet_simple(sheet, base_sheet_ole)
847
833
  else
848
- #not_given = WIN32OLE_VARIANT.new(nil, WIN32OLE::VARIANT::VT_NULL)
849
- #ole_workbook.Worksheets.Add(not_given,base_sheet.ole_worksheet)
850
834
  if base_sheet.name != last_sheet_local.name
851
- if sheet
852
- sheet.Copy(base_sheet.Next)
853
- else
854
- ole_workbook.Worksheets.Add(base_sheet.Next)
855
- end
835
+ add_or_copy_sheet_simple(sheet, base_sheet.Next)
856
836
  else
857
- if sheet
858
- sheet.Copy(base_sheet.ole_worksheet)
859
- else
860
- ole_workbook.Worksheets.Add(base_sheet.ole_worksheet)
861
- end
837
+ add_or_copy_sheet_simple(sheet, base_sheet_ole)
862
838
  base_sheet.Move(ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count-1))
863
839
  ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count).Activate
864
840
  end
@@ -867,14 +843,23 @@ module RobustExcelOle
867
843
  rescue WIN32OLERuntimeError, NameNotFound, Java::OrgRacobCom::ComFailException
868
844
  raise WorksheetREOError, "could not add given worksheet #{sheet.inspect}"
869
845
  end
870
- #ole_sheet = @excel.Activesheet
871
- ole_sheet = ole_workbook.Activesheet
872
- #ole_sheet = ole_sheet.nil? ? ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count) : ole_sheet
873
- new_sheet = worksheet_class.new(ole_sheet)
846
+ new_sheet = worksheet_class.new(ole_workbook.Activesheet)
874
847
  new_sheet.name = new_sheet_name if new_sheet_name
875
848
  new_sheet
876
849
  end
877
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
+
878
863
  # for compatibility to older versions
879
864
  def add_sheet(sheet = nil, opts = { })
880
865
  add_or_copy_sheet(sheet, opts)
@@ -904,30 +889,15 @@ module RobustExcelOle
904
889
  # @param [String] name the name of the range
905
890
  # @param [Variant] value the contents of the range
906
891
  def []= (name, value)
907
- old_color_if_modified = @color_if_modified
908
- workbook.color_if_modified = 42 # 42 - aqua-marin, 4-green
909
- set_namevalue_glob(name,value)
910
- workbook.color_if_modified = old_color_if_modified
892
+ set_namevalue_glob(name, value, :color => 42)
911
893
  end
912
894
 
913
895
  # sets options
914
896
  # @param [Hash] opts
915
897
  def for_this_workbook(opts)
916
898
  return unless alive?
917
- opts = self.class.process_options(opts, :use_defaults => false)
918
- visible_before = visible
919
- check_compatibility_before = check_compatibility
920
- unless opts[:read_only].nil?
921
- # if the ReadOnly status shall be changed, then close and reopen it
922
- if (!writable && !(opts[:read_only])) || (writable && opts[:read_only])
923
- opts[:check_compatibility] = check_compatibility if opts[:check_compatibility].nil?
924
- close(:if_unsaved => true)
925
- open_or_create_workbook(@stored_filename, opts)
926
- end
927
- end
928
- self.visible = opts[:force][:visible].nil? ? visible_before : opts[:force][:visible]
929
- self.CheckCompatibility = opts[:check_compatibility].nil? ? check_compatibility_before : opts[:check_compatibility]
930
- @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
931
901
  end
932
902
 
933
903
  # brings workbook to foreground, makes it available for heyboard inputs, makes the Excel instance visible
@@ -943,7 +913,6 @@ module RobustExcelOle
943
913
  true
944
914
  rescue
945
915
  @ole_workbook = nil # dead object won't be alive again
946
- # t $!.message
947
916
  false
948
917
  end
949
918
 
@@ -963,7 +932,7 @@ module RobustExcelOle
963
932
  end
964
933
 
965
934
  def calculation
966
- @excel.calculation if @ole_workbook
935
+ @excel.properties[:calculation] if @ole_workbook
967
936
  end
968
937
 
969
938
  # @private
@@ -973,7 +942,7 @@ module RobustExcelOle
973
942
 
974
943
  # returns true, if the workbook is visible, false otherwise
975
944
  def visible
976
- @excel.visible && @ole_workbook.Windows(@ole_workbook.Name).Visible
945
+ @excel.Visible && @ole_workbook.Windows(@ole_workbook.Name).Visible
977
946
  end
978
947
 
979
948
  # makes both the Excel instance and the window of the workbook visible, or the window invisible
@@ -1005,6 +974,7 @@ module RobustExcelOle
1005
974
  self.filename == other_book.filename
1006
975
  end
1007
976
 
977
+ # @private
1008
978
  def self.books
1009
979
  bookstore.books
1010
980
  end
@@ -1019,6 +989,11 @@ module RobustExcelOle
1019
989
  self.class.bookstore
1020
990
  end
1021
991
 
992
+ # @private
993
+ def workbook
994
+ self
995
+ end
996
+
1022
997
  # @private
1023
998
  def to_s
1024
999
  self.filename.to_s
@@ -1050,16 +1025,6 @@ module RobustExcelOle
1050
1025
  end
1051
1026
  end
1052
1027
 
1053
- # @private
1054
- def self.address_class
1055
- @address_class ||= begin
1056
- module_name = self.parent_name
1057
- "#{module_name}::Address".constantize
1058
- rescue NameError => e
1059
- Address
1060
- end
1061
- end
1062
-
1063
1028
  # @private
1064
1029
  def excel_class
1065
1030
  self.class.excel_class
@@ -1070,11 +1035,6 @@ module RobustExcelOle
1070
1035
  self.class.worksheet_class
1071
1036
  end
1072
1037
 
1073
- # @private
1074
- def address_class
1075
- self.class.address_class
1076
- end
1077
-
1078
1038
  include MethodHelpers
1079
1039
 
1080
1040
  private
@@ -1082,7 +1042,7 @@ module RobustExcelOle
1082
1042
  def method_missing(name, *args)
1083
1043
  if name.to_s[0,1] =~ /[A-Z]/
1084
1044
  raise ObjectNotAlive, 'method missing: workbook not alive' unless alive?
1085
- if JRUBY_BUG_ERRORMESSAGE
1045
+ if ::ERRORMESSAGE_JRUBY_BUG
1086
1046
  begin
1087
1047
  @ole_workbook.send(name, *args)
1088
1048
  rescue Java::OrgRacobCom::ComFailException
@@ -1104,6 +1064,47 @@ module RobustExcelOle
1104
1064
 
1105
1065
  public
1106
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
+
1107
1108
  Book = Workbook
1108
1109
 
1109
1110
  end