robust_excel_ole 1.15 → 1.16

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.
@@ -1,3 +1,3 @@
1
1
  module RobustExcelOle
2
- VERSION = "1.15"
2
+ VERSION = "1.16"
3
3
  end
@@ -12,32 +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
15
+
16
+ attr_reader :ole_workbook
17
+ attr_reader :excel
18
+ attr_reader :stored_filename
21
19
 
22
20
  alias ole_object ole_workbook
23
21
 
24
- DEFAULT_OPEN_OPTS = {
25
- :default => {:excel => :current},
22
+ CORE_DEFAULT_OPEN_OPTS = {
23
+ :default => {:excel => :current},
26
24
  :force => {},
27
- :update_links => :never,
25
+ :update_links => :never
26
+ }.freeze
27
+
28
+ DEFAULT_OPEN_OPTS = {
28
29
  :if_unsaved => :raise,
29
30
  :if_obstructed => :raise,
30
31
  :if_absent => :raise,
31
32
  :if_exists => :raise
32
- #:check_compatibility => false
33
- }.freeze
34
-
35
- CORE_DEFAULT_OPEN_OPTS = {
36
- :default => {:excel => :current}, :force => {}, :update_links => :never
37
- }.freeze
33
+ }.merge(CORE_DEFAULT_OPEN_OPTS).freeze
38
34
 
39
- ABBREVIATIONS = [[:default,:d], [:force, :f], [:excel, :e], [:visible, :v],
40
- [: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
41
42
 
42
43
 
43
44
  # opens a workbook.
@@ -83,51 +84,56 @@ module RobustExcelOle
83
84
  # :visible true -> makes the workbook visible
84
85
  # :check_compatibility true -> check compatibility when saving
85
86
  # :update_links true -> user is being asked how to update links, false -> links are never updated
86
- # @return [Workbook] a representation of a workbook
87
+ # @return [Workbook] a representation of a workbook
87
88
  def self.new(file_or_workbook, opts = { }, &block)
88
- options = process_options(opts)
89
- if file_or_workbook.is_a? WIN32OLE
89
+ process_options(opts)
90
+ case file_or_workbook
91
+ when NilClass
92
+ raise FileNameNotGiven, 'filename is nil'
93
+ when WIN32OLE
90
94
  file = file_or_workbook.Fullname.tr('\\','/')
91
- else
95
+ when Workbook
96
+ return file_or_workbook
97
+ when String
92
98
  file = file_or_workbook
93
- raise(FileNameNotGiven, 'filename is nil') if file.nil?
94
- 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'
95
102
  end
96
103
  # try to fetch the workbook from the bookstore
104
+ set_was_open opts, file_or_workbook.is_a?(WIN32OLE)
97
105
  book = nil
98
- if options[:force][:excel] != :new
106
+ if opts[:force][:excel] != :new
99
107
  # if readonly is true, then prefer a book that is given in force_excel if this option is set
100
108
  forced_excel =
101
- (options[:force][:excel].nil? || options[:force][:excel] == :current) ?
102
- (excel_class.new(:reuse => true) if !::JRUBY_BUG_CONNECT) : excel_of(options[:force][:excel])
109
+ (opts[:force][:excel].nil? || opts[:force][:excel] == :current) ?
110
+ (excel_class.new(:reuse => true) if !::CONNECT_JRUBY_BUG) : excel_of(opts[:force][:excel])
103
111
  begin
104
112
  book = if File.exists?(file)
105
- bookstore.fetch(file, :prefer_writable => !(options[:read_only]),
106
- :prefer_excel => (options[:read_only] ? forced_excel : nil))
113
+ bookstore.fetch(file, :prefer_writable => !(opts[:read_only]),
114
+ :prefer_excel => (opts[:read_only] ? forced_excel : nil))
107
115
  end
108
116
  rescue
117
+ raise
109
118
  trace "#{$!.message}"
110
119
  end
111
120
  if book
112
- # hack (unless condition): calling Worksheet[]= causes calling Worksheet#workbook which calls Workbook#new(ole_workbook)
113
- book.was_open = book.alive? unless file_or_workbook.is_a? WIN32OLE
121
+ set_was_open opts, book.alive?
114
122
  # drop the fetched workbook if it shall be opened in another Excel instance
115
123
  # 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)
124
+ if (opts[:force][:excel].nil? || opts[:force][:excel] == :current || forced_excel == book.excel) &&
125
+ !(book.alive? && !book.saved && (opts[:if_unsaved] != :accept))
126
+ opts[:force][:excel] = book.excel if book.excel && book.excel.alive?
127
+ book.ensure_workbook(file,opts)
128
+ book.set_options(file,opts)
121
129
  return book
122
130
  end
123
131
  end
124
132
  end
125
- super(file_or_workbook, options, &block)
133
+ super(file_or_workbook, opts, &block)
126
134
  end
127
135
 
128
- def self.open(file_or_workbook, opts = { }, &block)
129
- new(file_or_workbook, opts, &block)
130
- end
136
+ singleton_class.send :alias_method, :open, :new
131
137
 
132
138
  # creates a new Workbook object, if a file name is given
133
139
  # Promotes the win32ole workbook to a Workbook object, if a win32ole-workbook is given
@@ -135,7 +141,7 @@ module RobustExcelOle
135
141
  # @param [Hash] opts
136
142
  # @option opts [Symbol] see above
137
143
  # @return [Workbook] a workbook
138
- def initialize(file_or_workbook, options = { }, &block)
144
+ def initialize(file_or_workbook, options, &block)
139
145
  if file_or_workbook.is_a? WIN32OLE
140
146
  @ole_workbook = file_or_workbook
141
147
  ole_excel = begin
@@ -150,8 +156,8 @@ module RobustExcelOle
150
156
  ensure_workbook(filename, options)
151
157
  end
152
158
  set_options(filename, options)
153
- bookstore.store(self)
154
- r1c1_letters = @ole_workbook.Worksheets.Item(1).Cells.Item(1,1).Address(true,true,XlR1C1).gsub(/[0-9]/,'') #('ReferenceStyle' => XlR1C1).gsub(/[0-9]/,'')
159
+ store_myself
160
+ r1c1_letters = @ole_workbook.Worksheets.Item(1).Cells.Item(1,1).Address(true,true,XlR1C1).gsub(/[0-9]/,'')
155
161
  address_class.new(r1c1_letters)
156
162
  if block
157
163
  begin
@@ -164,41 +170,53 @@ module RobustExcelOle
164
170
 
165
171
  private
166
172
 
173
+ # @private
174
+ def self.set_was_open(hash, value)
175
+ hash[:was_open] = value if hash.has_key?(:was_open)
176
+ end
177
+
178
+ # @private
179
+ def set_was_open(hash, value)
180
+ self.class.set_was_open(hash, value)
181
+ end
182
+
183
+ # @private
167
184
  # translates abbreviations and synonyms and merges with default options
168
- def self.process_options(options, proc_opts = {:use_defaults => true})
169
- translator = proc do |opts|
170
- erg = {}
171
- opts.each do |key,value|
172
- new_key = key
173
- ABBREVIATIONS.each { |long,short| new_key = long if key == short }
174
- if value.is_a?(Hash)
175
- erg[new_key] = {}
176
- value.each do |k,v|
177
- new_k = k
178
- ABBREVIATIONS.each { |l,s| new_k = l if k == s }
179
- erg[new_key][new_k] = v
180
- end
181
- else
182
- erg[new_key] = value
185
+ def self.process_options(opts, proc_opts = {:use_defaults => true})
186
+ translate(opts)
187
+ default_opts = (proc_opts[:use_defaults] ? DEFAULT_OPEN_OPTS : CORE_DEFAULT_OPEN_OPTS).dup
188
+ translate(default_opts)
189
+ opts.merge!(default_opts) {|key, v1, v2| v1 }
190
+ opts[:default] = default_opts[:default].merge(opts[:default]) unless opts[:default].nil?
191
+ opts[:force] = default_opts[:force].merge(opts[:force]) unless opts[:force].nil?
192
+ end
193
+
194
+ # @private
195
+ def self.translate(opts)
196
+ erg = {}
197
+ opts.each do |key,value|
198
+ new_key = key
199
+ ABBREVIATIONS.each { |long,short| new_key = long if key == short }
200
+ if value.is_a?(Hash)
201
+ erg[new_key] = {}
202
+ value.each do |k,v|
203
+ new_k = k
204
+ ABBREVIATIONS.each { |l,s| new_k = l if k == s }
205
+ erg[new_key][new_k] = v
183
206
  end
207
+ else
208
+ erg[new_key] = value
184
209
  end
185
- erg[:default] ||= {}
186
- erg[:force] ||= {}
187
- force_list = [:visible, :excel]
188
- erg.each { |key,value| erg[:force][key] = value if force_list.include?(key) }
189
- erg[:default][:excel] = erg[:default_excel] unless erg[:default_excel].nil?
190
- erg[:force][:excel] = erg[:force_excel] unless erg[:force_excel].nil?
191
- erg[:default][:excel] = :current if erg[:default][:excel] == :reuse || erg[:default][:excel] == :active
192
- erg[:force][:excel] = :current if erg[:force][:excel] == :reuse || erg[:force][:excel] == :active
193
- erg
194
210
  end
195
- opts = translator.call(options)
196
- default_open_opts = proc_opts[:use_defaults] ? DEFAULT_OPEN_OPTS : CORE_DEFAULT_OPEN_OPTS
197
- default_opts = translator.call(default_open_opts)
198
- opts = default_opts.merge(opts)
199
- opts[:default] = default_opts[:default].merge(opts[:default]) unless opts[:default].nil?
200
- opts[:force] = default_opts[:force].merge(opts[:force]) unless opts[:force].nil?
201
- opts
211
+ opts.merge!(erg)
212
+ opts[:default] ||= {}
213
+ opts[:force] ||= {}
214
+ force_list = [:visible, :excel]
215
+ opts.each { |key,value| opts[:force][key] = value if force_list.include?(key) }
216
+ opts[:default][:excel] = opts[:default_excel] unless opts[:default_excel].nil?
217
+ opts[:force][:excel] = opts[:force_excel] unless opts[:force_excel].nil?
218
+ opts[:default][:excel] = :current if opts[:default][:excel] == :reuse || opts[:default][:excel] == :active
219
+ opts[:force][:excel] = :current if opts[:force][:excel] == :reuse || opts[:force][:excel] == :active
202
220
  end
203
221
 
204
222
  # returns an Excel object when given Excel, Workbook or Win32ole object representing a Workbook or an Excel
@@ -231,7 +249,9 @@ module RobustExcelOle
231
249
 
232
250
  # @private
233
251
  def ensure_workbook(filename, options)
252
+ set_was_open options, true
234
253
  return if (@ole_workbook && alive? && (options[:read_only].nil? || @ole_workbook.ReadOnly == options[:read_only]))
254
+ set_was_open options, false
235
255
  if options[:if_unsaved]==:accept &&
236
256
  ((options[:read_only]==true && self.ReadOnly==false) || (options[:read_only]==false && self.ReadOnly==true))
237
257
  raise OptionInvalid, ":if_unsaved:accept and change of read-only mode is not possible"
@@ -242,16 +262,14 @@ module RobustExcelOle
242
262
  ensure_excel(options)
243
263
  workbooks = @excel.Workbooks
244
264
  @ole_workbook = workbooks.Item(File.basename(filename)) rescue nil if @ole_workbook.nil?
245
- if @ole_workbook
246
- @was_open = true if @was_open.nil? # necessary?
265
+ if @ole_workbook && alive?
266
+ set_was_open options, true #if @was_open.nil? # necessary?
247
267
  manage_blocking_or_unsaved_workbook(filename,options)
248
268
  open_or_create_workbook(filename,options) if @ole_workbook.ReadOnly != options[:read_only]
249
269
  else
250
270
  if excel_option.nil? || excel_option == :current &&
251
- (!::JRUBY_BUG_CONNECT || filename[0] != '/')
252
- workbooks_number_before_connect = @excel.Workbooks.Count
271
+ (!::CONNECT_JRUBY_BUG || filename[0] != '/')
253
272
  connect(filename,options)
254
- @was_open = @excel.Workbooks.Count == workbooks_number_before_connect
255
273
  else
256
274
  open_or_create_workbook(filename,options)
257
275
  end
@@ -275,15 +293,10 @@ module RobustExcelOle
275
293
 
276
294
  # @private
277
295
  # connects to an unknown workbook
278
- def connect(filename,options)
279
- excels_number = excel_class.excels_number
280
- workbooks_number = if excels_number>0
281
- excel_class.current.Workbooks.Count
282
- else 0
283
- end
284
- abs_filename = General.absolute_path(filename)
296
+ def connect(filename,options)
297
+ workbooks_number = excel_class.excels_number==0 ? 0 : excel_class.current.Workbooks.Count
285
298
  @ole_workbook = begin
286
- WIN32OLE.connect(abs_filename)
299
+ WIN32OLE.connect(General.absolute_path(filename))
287
300
  rescue
288
301
  if $!.message =~ /moniker/
289
302
  raise WorkbookConnectingBlockingError
@@ -300,6 +313,7 @@ module RobustExcelOle
300
313
  raise WorkbookConnectingUnknownError
301
314
  end
302
315
  end
316
+ set_was_open options, (ole_excel.Workbooks.Count == workbooks_number)
303
317
  @excel = excel_class.new(ole_excel)
304
318
  end
305
319
 
@@ -324,9 +338,11 @@ module RobustExcelOle
324
338
 
325
339
  # @private
326
340
  def manage_blocking_or_unsaved_workbook(filename,options)
327
- obstructed_by_other_book = if (File.basename(filename) == File.basename(@ole_workbook.Fullname))
328
- General.absolute_path(filename) != @ole_workbook.Fullname
329
- end
341
+ filename = General.absolute_path(filename)
342
+ filename = General.canonize(filename)
343
+ previous_file = General.canonize(@ole_workbook.Fullname)
344
+ obstructed_by_other_book = (File.basename(filename) == File.basename(previous_file)) &&
345
+ (File.dirname(filename) != File.dirname(previous_file))
330
346
  if obstructed_by_other_book
331
347
  # workbook is being obstructed by a workbook with same name and different path
332
348
  manage_blocking_workbook(filename,options)
@@ -415,7 +431,6 @@ module RobustExcelOle
415
431
  begin
416
432
  abs_filename = General.absolute_path(filename)
417
433
  begin
418
- @was_open = false if @was_open.nil?
419
434
  workbooks = @excel.Workbooks
420
435
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
421
436
  raise UnexpectedREOError, "cannot access workbooks: #{msg.message} #{msg.backtrace}"
@@ -427,7 +442,7 @@ module RobustExcelOle
427
442
  updatelinks_vba(options[:update_links]),
428
443
  options[:read_only] )
429
444
  end
430
- rescue WIN32OLERuntimeError => msg
445
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
431
446
  # for Excel2007: for option :if_unsaved => :alert and user cancels: this error appears?
432
447
  # if yes: distinguish these events
433
448
  raise UnexpectedREOError, "cannot open workbook: #{msg.message} #{msg.backtrace}"
@@ -436,10 +451,10 @@ module RobustExcelOle
436
451
  # workaround for bug in Excel 2010: workbook.Open does not always return the workbook when given file name
437
452
  begin
438
453
  @ole_workbook = workbooks.Item(File.basename(filename))
439
- rescue WIN32OLERuntimeError => msg
454
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
440
455
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message}"
441
456
  end
442
- rescue WIN32OLERuntimeError => msg
457
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
443
458
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message} #{msg.backtrace}"
444
459
  end
445
460
  end
@@ -451,10 +466,10 @@ module RobustExcelOle
451
466
  # parameter 'UpdateLinks' has no effect
452
467
  def updatelinks_vba(updatelinks_reo)
453
468
  case updatelinks_reo
454
- when :alert then RobustExcelOle::XlUpdateLinksUserSetting
455
- when :never then RobustExcelOle::XlUpdateLinksNever
469
+ when :alert then RobustExcelOle::XlUpdateLinksUserSetting
470
+ when :never then RobustExcelOle::XlUpdateLinksNever
456
471
  when :always then RobustExcelOle::XlUpdateLinksAlways
457
- else RobustExcelOle::XlUpdateLinksNever
472
+ else RobustExcelOle::XlUpdateLinksNever
458
473
  end
459
474
  end
460
475
 
@@ -583,7 +598,7 @@ module RobustExcelOle
583
598
 
584
599
  @private
585
600
  def self.unobtrusively_opening(file, opts, book_is_alive, &block)
586
- opts = process_options(opts)
601
+ process_options(opts)
587
602
  opts = {:if_closed => :current, :keep_open => false}.merge(opts)
588
603
  raise OptionInvalid, 'contradicting options' if opts[:writable] && opts[:read_only]
589
604
  if book_is_alive.nil?
@@ -598,6 +613,7 @@ module RobustExcelOle
598
613
  end
599
614
  open_opts = excel_opts.merge({:if_unsaved => :accept})
600
615
  begin
616
+ open_opts[:was_open] = nil
601
617
  book = open(file, open_opts)
602
618
  was_visible = book.visible
603
619
  was_writable = book.writable
@@ -614,13 +630,14 @@ module RobustExcelOle
614
630
  book.set_options(file, opts.merge({:read_only => !was_writable,
615
631
  :if_unsaved => (opts[:writable]==false ? :forget : :save)}))
616
632
  end
617
- if book.was_open
633
+ was_open = open_opts[:was_open]
634
+ if was_open
618
635
  book.visible = was_visible
619
636
  book.CheckCompatibility = was_check_compatibility
620
637
  book.excel.calculation = was_calculation
621
638
  end
622
- book.Saved = (was_saved || !book.was_open)
623
- book.close unless book.was_open || opts[:keep_open]
639
+ book.Saved = (was_saved || !was_open)
640
+ book.close unless was_open || opts[:keep_open]
624
641
  end
625
642
  end
626
643
  end
@@ -671,12 +688,12 @@ module RobustExcelOle
671
688
  # :close_if_saved -> closes the blocking workbook, if it is saved,
672
689
  # otherwise raises an exception
673
690
  # @return [Workbook], the book itself, if successfully saved, raises an exception otherwise
674
- def save_as(file, opts = { })
691
+ def save_as(file, options = { })
675
692
  raise FileNameNotGiven, 'filename is nil' if file.nil?
676
693
  raise ObjectNotAlive, 'workbook is not alive' unless alive?
677
694
  raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
678
695
  raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
679
- options = self.class.process_options(opts)
696
+ self.class.process_options(options)
680
697
  if File.exist?(file)
681
698
  case options[:if_exists]
682
699
  when :overwrite
@@ -731,6 +748,11 @@ module RobustExcelOle
731
748
 
732
749
  private
733
750
 
751
+ def store_myself
752
+ bookstore.store(self)
753
+ @stored_filename = filename
754
+ end
755
+
734
756
  # @private
735
757
  def save_as_workbook(file, options)
736
758
  dirname, basename = File.split(file)
@@ -741,7 +763,7 @@ module RobustExcelOle
741
763
  when '.xlsm' then RobustExcelOle::XlOpenXMLWorkbookMacroEnabled
742
764
  end
743
765
  @ole_workbook.SaveAs(General.absolute_path(file), file_format)
744
- bookstore.store(self)
766
+ store_myself
745
767
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
746
768
  if msg.message =~ /SaveAs/ && msg.message =~ /Workbook/
747
769
  # trace "save: canceled by user" if options[:if_exists] == :alert || options[:if_exists] == :excel
@@ -820,7 +842,7 @@ module RobustExcelOle
820
842
  last_sheet_local = last_sheet
821
843
  after_or_before, base_sheet = opts.to_a.first || [:after, last_sheet_local]
822
844
  begin
823
- if !::JRUBY_BUG_COPYSHEETS
845
+ if !::COPYSHEETS_JRUBY_BUG
824
846
  if sheet
825
847
  sheet.Copy({ after_or_before.to_s => base_sheet.ole_worksheet })
826
848
  else
@@ -857,9 +879,7 @@ module RobustExcelOle
857
879
  rescue WIN32OLERuntimeError, NameNotFound, Java::OrgRacobCom::ComFailException
858
880
  raise WorksheetREOError, "could not add given worksheet #{sheet.inspect}"
859
881
  end
860
- #ole_sheet = @excel.Activesheet
861
882
  ole_sheet = ole_workbook.Activesheet
862
- #ole_sheet = ole_sheet.nil? ? ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count) : ole_sheet
863
883
  new_sheet = worksheet_class.new(ole_sheet)
864
884
  new_sheet.name = new_sheet_name if new_sheet_name
865
885
  new_sheet
@@ -894,17 +914,14 @@ module RobustExcelOle
894
914
  # @param [String] name the name of the range
895
915
  # @param [Variant] value the contents of the range
896
916
  def []= (name, value)
897
- old_color_if_modified = @color_if_modified
898
- workbook.color_if_modified = 42 # 42 - aqua-marin, 4-green
899
- set_namevalue_glob(name,value)
900
- workbook.color_if_modified = old_color_if_modified
917
+ set_namevalue_glob(name,value,:color => 42)
901
918
  end
902
919
 
903
920
  # sets options
904
921
  # @param [Hash] opts
905
922
  def for_this_workbook(opts)
906
923
  return unless alive?
907
- opts = self.class.process_options(opts, :use_defaults => false)
924
+ self.class.process_options(opts, :use_defaults => false)
908
925
  visible_before = visible
909
926
  check_compatibility_before = check_compatibility
910
927
  unless opts[:read_only].nil?
@@ -1077,7 +1094,7 @@ module RobustExcelOle
1077
1094
  def method_missing(name, *args)
1078
1095
  if name.to_s[0,1] =~ /[A-Z]/
1079
1096
  raise ObjectNotAlive, 'method missing: workbook not alive' unless alive?
1080
- if ::JRUBY_BUG_ERRORMESSAGE
1097
+ if ::ERRORMESSAGE_JRUBY_BUG
1081
1098
  begin
1082
1099
  @ole_workbook.send(name, *args)
1083
1100
  rescue Java::OrgRacobCom::ComFailException