robust_excel_ole 1.15 → 1.16

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