robust_excel_ole 1.8 → 1.9

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.8"
2
+ VERSION = "1.9"
3
3
  end
@@ -85,14 +85,12 @@ module RobustExcelOle
85
85
  if (options[:force][:excel] != :new) && (options[:force][:excel] != :reserved_new)
86
86
  # if readonly is true, then prefer a book that is given in force_excel if this option is set
87
87
  forced_excel = if options[:force][:excel]
88
- options[:force][:excel] == :current ? excel_class.new(:reuse => true) : excel_of(options[:force][:excel])
88
+ options[:force][:excel] == :current ? excel_class.new(:reuse => true) : excel_of(options[:force][:excel])
89
89
  end
90
90
  book = bookstore.fetch(file,
91
91
  :prefer_writable => !(options[:read_only]),
92
92
  :prefer_excel => (options[:read_only] ? forced_excel : nil)) rescue nil
93
93
  if book
94
- # if forced_excel != book.excel &&
95
- # (not (book.alive? && (not book.saved) && (not options[:if_unsaved] == :accept)))
96
94
  if (!(options[:force][:excel]) || (forced_excel == book.excel)) &&
97
95
  !(book.alive? && !book.saved && (options[:if_unsaved] != :accept))
98
96
  book.options = options
@@ -116,24 +114,26 @@ module RobustExcelOle
116
114
  end
117
115
  end
118
116
 
119
- # creates a Workbook object by opening an Excel file given its filename workbook
117
+ # creates a Workbook object by opening an Excel file given its filename
120
118
  # or by promoting a Win32OLE object representing an Excel file
121
- # @param [WIN32OLE] workbook a workbook
122
- # @param [Hash] opts the options
123
- # @option opts [Symbol] see above
119
+ # @param [WIN32OLE] workbook a Win32Ole-workbook
120
+ # @param [Hash] opts the options as in Workbook::open
121
+ # @option opts [Symbol] see Workbook::open
124
122
  # @return [Workbook] a workbook
125
- def self.new(workbook, opts = { }, &block)
123
+ def self.new(ole_workbook, opts = { }, &block)
126
124
  opts = process_options(opts)
127
- if workbook && (workbook.is_a? WIN32OLE)
128
- filename = workbook.Fullname.tr('\\','/') rescue nil
125
+ if ole_workbook && (ole_workbook.is_a? WIN32OLE)
126
+ filename = ole_workbook.Fullname.tr('\\','/') rescue nil
129
127
  if filename
130
128
  book = bookstore.fetch(filename)
131
129
  if book && book.alive?
132
- book.visible = opts[:force][:visible] unless opts[:force].nil? || opts[:force][:visible].nil?
133
- book.excel.calculation = opts[:calculation] unless opts[:calculation].nil?
130
+ open(filename, opts)
131
+ #book.visible = opts[:force][:visible] unless opts[:force].nil? || opts[:force][:visible].nil?
132
+ #book.excel.calculation = opts[:calculation] unless opts[:calculation].nil?
133
+ #book.excel.displayalerts = opts[:calculation] unless opts[:calculation].nil?
134
134
  return book
135
135
  else
136
- super
136
+ super(filename,opts)
137
137
  end
138
138
  end
139
139
  else
@@ -144,7 +144,7 @@ module RobustExcelOle
144
144
  # creates a new Workbook object, if a file name is given
145
145
  # Promotes the win32ole workbook to a Workbook object, if a win32ole-workbook is given
146
146
  # @param [Variant] file_or_workbook file name or workbook
147
- # @param [Hash] opts the options
147
+ # @param [Hash] opts the options as in Workbook::open
148
148
  # @option opts [Symbol] see above
149
149
  # @return [Workbook] a workbook
150
150
  def initialize(file_or_workbook, options = { }, &block)
@@ -166,6 +166,8 @@ module RobustExcelOle
166
166
  bookstore.store(self)
167
167
  @modified_cells = []
168
168
  @workbook = @excel.workbook = self
169
+ r1c1_letters = @ole_workbook.Worksheets.Item(1).Cells.Item(1,1).Address('ReferenceStyle' => XlR1C1).gsub(/[0-9]/,'')
170
+ address_class.new(r1c1_letters)
169
171
  if block
170
172
  begin
171
173
  yield self
@@ -240,7 +242,7 @@ module RobustExcelOle
240
242
 
241
243
  # @private
242
244
  def ensure_excel(options)
243
- if excel && @excel.alive?
245
+ if @excel && @excel.alive?
244
246
  @excel.created = false
245
247
  return
246
248
  end
@@ -251,85 +253,133 @@ module RobustExcelOle
251
253
  @excel
252
254
  end
253
255
 
256
+
254
257
  # @private
255
- def ensure_workbook(file, options)
256
- file = @stored_filename ? @stored_filename : file
257
- raise(FileNameNotGiven, 'filename is nil') if file.nil?
258
- raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
259
- unless File.exist?(file)
260
- if options[:if_absent] == :create
261
- @ole_workbook = excel_class.current.generate_workbook(file)
262
- else
263
- raise FileNotFound, "file #{General.absolute_path(file).inspect} not found"
258
+ def ensure_workbook(filename, options)
259
+ filename = @stored_filename ? @stored_filename : filename
260
+ raise(FileNameNotGiven, 'filename is nil') if filename.nil?
261
+ if File.directory?(filename)
262
+ raise(FileNotFound, "file #{General.absolute_path(filename).inspect} is a directory")
263
+ end
264
+ manage_nonexisting_file(filename,options)
265
+ workbooks = @excel.Workbooks
266
+ @ole_workbook = workbooks.Item(File.basename(filename)) rescue nil if @ole_workbook.nil?
267
+ if @ole_workbook
268
+ manage_blocking_or_unsaved_workbook(filename,options)
269
+ else
270
+ open_or_create_workbook(filename, options)
271
+ end
272
+ end
273
+
274
+ private
275
+
276
+ # @private
277
+ def manage_nonexisting_file(filename,options)
278
+ return if File.exist?(filename)
279
+ if options[:if_absent] == :create
280
+ excel.Workbooks.Add
281
+ empty_ole_workbook = excel.Workbooks.Item(excel.Workbooks.Count)
282
+ abs_filename = General.absolute_path(filename).tr('/','\\')
283
+ begin
284
+ empty_ole_workbook.SaveAs(abs_filename)
285
+ rescue WIN32OLERuntimeError => msg
286
+ raise FileNotFound, "could not save workbook with filename #{filename.inspect}"
264
287
  end
288
+ else
289
+ raise FileNotFound, "file #{abs_filename.inspect} not found" +
290
+ "\nHint: If you want to create a new file, use option :if_absent => :create or Workbook::create"
265
291
  end
266
- @ole_workbook = @excel.Workbooks.Item(File.basename(file)) rescue nil
267
- if @ole_workbook
268
- obstructed_by_other_book = (File.basename(file) == File.basename(@ole_workbook.Fullname)) &&
269
- (General.absolute_path(file) != @ole_workbook.Fullname)
270
- # if workbook is obstructed by a workbook with same name and different path
271
- if obstructed_by_other_book
272
- case options[:if_obstructed]
273
- when :raise
274
- raise WorkbookBlocked, "blocked by a workbook with the same name in a different path: #{@ole_workbook.Fullname.tr('\\','/')}"
275
- when :forget
276
- @ole_workbook.Close
277
- @ole_workbook = nil
278
- open_or_create_workbook(file, options)
279
- when :save
280
- save unless @ole_workbook.Saved
281
- @ole_workbook.Close
282
- @ole_workbook = nil
283
- open_or_create_workbook(file, options)
284
- when :close_if_saved
285
- if !@ole_workbook.Saved
286
- raise WorkbookBlocked, "workbook with the same name in a different path is unsaved: #{@ole_workbook.Fullname.tr('\\','/')}"
287
- else
288
- @ole_workbook.Close
289
- @ole_workbook = nil
290
- open_or_create_workbook(file, options)
291
- end
292
- when :new_excel
293
- @excel = excel_class.new(:reuse => false)
294
- open_or_create_workbook(file, options)
295
- else
296
- raise OptionInvalid, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}"
297
- end
292
+ end
293
+
294
+ # @private
295
+ def manage_blocking_or_unsaved_workbook(filename,options)
296
+ obstructed_by_other_book = if (File.basename(filename) == File.basename(@ole_workbook.Fullname))
297
+ p1 = General.absolute_path(filename)
298
+ p2 = @ole_workbook.Fullname
299
+ is_same_path = if p1[1..1] == ":" and p2[0..1] == '\\\\' # normal path starting with the drive letter and a path in network notation (starting with 2 backslashes)
300
+ # this is a Workaround for Excel2010 on WinXP: Network drives won't get translated to drive letters. So we'll do it manually:
301
+ p1_pure_path = p1[2..-1]
302
+ p2.end_with?(p1_pure_path) and p2[0, p2.rindex(p1_pure_path)] =~ /^\\\\[\w\d_]+(\\[\w\d_]+)*$/ # e.g. "\\\\server\\folder\\subfolder"
298
303
  else
299
- # book open, not obstructed by an other book, but not saved and writable
300
- unless @ole_workbook.Saved
301
- case options[:if_unsaved]
302
- when :raise
303
- raise WorkbookNotSaved, "workbook is already open but not saved: #{File.basename(file).inspect}"
304
- when :forget
305
- @ole_workbook.Close
306
- @ole_workbook = nil
307
- open_or_create_workbook(file, options)
308
- when :accept
309
- # do nothing
310
- when :alert, :excel
311
- @excel.with_displayalerts(true) { open_or_create_workbook(file,options) }
312
- when :new_excel
313
- @excel = excel_class.new(:reuse => false)
314
- open_or_create_workbook(file, options)
315
- else
316
- raise OptionInvalid, ":if_unsaved: invalid option: #{options[:if_unsaved].inspect}"
317
- end
318
- end
304
+ p1 == p2
319
305
  end
306
+ not is_same_path
307
+ end
308
+ if obstructed_by_other_book
309
+ # workbook is being obstructed by a workbook with same name and different path
310
+ manage_blocking_workbook(filename,options)
320
311
  else
321
- # open a new workbook
322
- open_or_create_workbook(file, options)
312
+ unless @ole_workbook.Saved
313
+ # workbook open and writable, not obstructed by another workbook, but not saved
314
+ manage_unsaved_workbook(filename,options)
315
+ end
316
+ end
317
+ end
318
+
319
+ # @private
320
+ def manage_blocking_workbook(filename,options)
321
+ case options[:if_obstructed]
322
+ when :raise
323
+ raise WorkbookBlocked, "blocked by a workbook with the same name in a different path: #{@ole_workbook.Fullname.tr('\\','/')}" +
324
+ "\nHint: Use the option :if_obstructed with values :forget or :save,
325
+ to close the old workbook, without or with saving before, respectively,
326
+ and to open the new workbook"
327
+ when :forget
328
+ @ole_workbook.Close
329
+ @ole_workbook = nil
330
+ open_or_create_workbook(filename, options)
331
+ when :save
332
+ save unless @ole_workbook.Saved
333
+ @ole_workbook.Close
334
+ @ole_workbook = nil
335
+ open_or_create_workbook(filename, options)
336
+ when :close_if_saved
337
+ if !@ole_workbook.Saved
338
+ raise WorkbookBlocked, "workbook with the same name in a different path is unsaved: #{@ole_workbook.Fullname.tr('\\','/')}"
339
+ else
340
+ @ole_workbook.Close
341
+ @ole_workbook = nil
342
+ open_or_create_workbook(filename, options)
343
+ end
344
+ when :new_excel
345
+ @excel = excel_class.new(:reuse => false)
346
+ open_or_create_workbook(filename, options)
347
+ else
348
+ raise OptionInvalid, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}" +
349
+ "\nHint: Valid values are :raise, :forget, :save, :close_if_saved, :new_excel"
350
+ end
351
+ end
352
+
353
+ def manage_unsaved_workbook(filename,options)
354
+ case options[:if_unsaved]
355
+ when :raise
356
+ raise WorkbookNotSaved, "workbook is already open but not saved: #{File.basename(filename).inspect}" +
357
+ "\nHint: Save the workbook or open the workbook using option :if_unsaved with values :forget and :accept to
358
+ close the unsaved workbook and reopen it, or to let the unsaved workbook open, respectively"
359
+ when :forget
360
+ @ole_workbook.Close
361
+ @ole_workbook = nil
362
+ open_or_create_workbook(filename, options)
363
+ when :accept
364
+ # do nothing
365
+ when :alert, :excel
366
+ @excel.with_displayalerts(true) { open_or_create_workbook(filename,options) }
367
+ when :new_excel
368
+ @excel = excel_class.new(:reuse => false)
369
+ open_or_create_workbook(filename, options)
370
+ else
371
+ raise OptionInvalid, ":if_unsaved: invalid option: #{options[:if_unsaved].inspect}" +
372
+ "\nHint: Valid values are :raise, :forget, :accept, :alert, :excel, :new_excel"
323
373
  end
324
374
  end
325
375
 
326
376
  private
327
377
 
328
378
  # @private
329
- def open_or_create_workbook(file, options)
379
+ def open_or_create_workbook(filename, options)
330
380
  if !@ole_workbook || (options[:if_unsaved] == :alert) || options[:if_obstructed]
331
381
  begin
332
- filename = General.absolute_path(file)
382
+ abs_filename = General.absolute_path(filename)
333
383
  begin
334
384
  workbooks = @excel.Workbooks
335
385
  rescue WIN32OLERuntimeError => msg
@@ -337,8 +387,8 @@ module RobustExcelOle
337
387
  end
338
388
  begin
339
389
  with_workaround_linked_workbooks_excel2007(options) do
340
- workbooks.Open(filename, { 'ReadOnly' => options[:read_only],
341
- 'UpdateLinks' => updatelinks_vba(options[:update_links]) })
390
+ workbooks.Open(abs_filename, { 'ReadOnly' => options[:read_only] })
391
+ # 'UpdateLinks' => updatelinks_vba(options[:update_links]) })
342
392
  end
343
393
  rescue WIN32OLERuntimeError => msg
344
394
  # for Excel2007: for option :if_unsaved => :alert and user cancels: this error appears?
@@ -405,6 +455,13 @@ module RobustExcelOle
405
455
 
406
456
  public
407
457
 
458
+ # creates, i.e., opens a new, empty workbook, and saves it under a given filename
459
+ # @param [String] filename the filename under which the new workbook should be saved
460
+ # @param [Hash] opts the options as in Workbook::open
461
+ def self.create(filename, opts = { })
462
+ open(filename, :if_absent => :create)
463
+ end
464
+
408
465
  # closes the workbook, if it is alive
409
466
  # @param [Hash] opts the options
410
467
  # @option opts [Symbol] :if_unsaved :raise (default), :save, :forget, :keep_open, or :alert
@@ -421,7 +478,8 @@ module RobustExcelOle
421
478
  if alive? && !@ole_workbook.Saved && writable
422
479
  case opts[:if_unsaved]
423
480
  when :raise
424
- raise WorkbookNotSaved, "workbook is unsaved: #{File.basename(self.stored_filename).inspect}"
481
+ raise WorkbookNotSaved, "workbook is unsaved: #{File.basename(self.stored_filename).inspect}" +
482
+ "\nHint: Use option :save or :forget to close the workbook with or without saving"
425
483
  when :save
426
484
  save
427
485
  close_workbook
@@ -432,7 +490,8 @@ module RobustExcelOle
432
490
  when :alert, :excel
433
491
  @excel.with_displayalerts(true) { close_workbook }
434
492
  else
435
- raise OptionInvalid, ":if_unsaved: invalid option: #{opts[:if_unsaved].inspect}"
493
+ raise OptionInvalid, ":if_unsaved: invalid option: #{opts[:if_unsaved].inspect}" +
494
+ "\nHint: Valid values are :raise, :save, :keep_open, :alert, :excel"
436
495
  end
437
496
  else
438
497
  close_workbook
@@ -554,7 +613,11 @@ module RobustExcelOle
554
613
  # @return [Boolean] true, if successfully saved, nil otherwise
555
614
  def save(opts = {:discoloring => false})
556
615
  raise ObjectNotAlive, 'workbook is not alive' unless alive?
557
- raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
616
+ raise WorkbookReadOnly, 'Not opened for writing (opened with :read_only option)' if @ole_workbook.ReadOnly
617
+ # if you have open the workbook with :read_only => true,
618
+ # then you could close the workbook and open it again with option :read_only => false
619
+ # otherwise the workbook may already be open writable in an another Excel instance
620
+ # then you could use this workbook or close the workbook there
558
621
  begin
559
622
  discoloring if opts[:discoloring]
560
623
  @modified_cells = []
@@ -605,7 +668,7 @@ module RobustExcelOle
605
668
  begin
606
669
  File.delete(file)
607
670
  rescue Errno::EACCES
608
- raise WorkbookBeingUsed, 'workbook is open and used in Excel'
671
+ raise WorkbookBeingUsed, 'workbook is open and being used in an Excel instance'
609
672
  end
610
673
  end
611
674
  when :alert, :excel
@@ -614,24 +677,32 @@ module RobustExcelOle
614
677
  end
615
678
  return self
616
679
  when :raise
617
- raise FileAlreadyExists, "file already exists: #{File.basename(file).inspect}"
680
+ raise FileAlreadyExists, "file already exists: #{File.basename(file).inspect}" +
681
+ "\nHint: Use option :if_exists => :overwrite, if you want to overwrite the file"
618
682
  else
619
- raise OptionInvalid, ":if_exists: invalid option: #{options[:if_exists].inspect}"
683
+ raise OptionInvalid, ":if_exists: invalid option: #{options[:if_exists].inspect}" +
684
+ "\nHint: Valid values are :raise, :overwrite, :alert, :excel"
620
685
  end
621
686
  end
622
687
  other_workbook = @excel.Workbooks.Item(File.basename(file)) rescue nil
623
688
  if other_workbook && self.filename != other_workbook.Fullname.tr('\\','/')
624
689
  case options[:if_obstructed]
625
690
  when :raise
626
- raise WorkbookBlocked, "blocked by another workbook: #{other_workbook.Fullname.tr('\\','/')}"
691
+ raise WorkbookBlocked, "blocked by another workbook: #{other_workbook.Fullname.tr('\\','/')}" +
692
+ "\nHint: Use the option :if_obstructed with values :forget or :save to
693
+ close or save and close the blocking workbook"
627
694
  when :forget
628
695
  # nothing
629
696
  when :save
630
697
  other_workbook.Save
631
698
  when :close_if_saved
632
- raise WorkbookBlocked, "blocking workbook is unsaved: #{File.basename(file).inspect}" unless other_workbook.Saved
699
+ unless other_workbook.Saved
700
+ raise WorkbookBlocked, "blocking workbook is unsaved: #{File.basename(file).inspect}" +
701
+ "\nHint: Use option :if_obstructed => :save to save the blocking workbooks"
702
+ end
633
703
  else
634
- raise OptionInvalid, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}"
704
+ raise OptionInvalid, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}" +
705
+ "\nHint: Valid values are :raise, :forget, :save, :close_if_saved"
635
706
  end
636
707
  other_workbook.Close
637
708
  end
@@ -928,6 +999,16 @@ module RobustExcelOle
928
999
  end
929
1000
  end
930
1001
 
1002
+ # @private
1003
+ def self.address_class
1004
+ @address_class ||= begin
1005
+ module_name = self.parent_name
1006
+ "#{module_name}::Address".constantize
1007
+ rescue NameError => e
1008
+ Address
1009
+ end
1010
+ end
1011
+
931
1012
  # @private
932
1013
  def excel_class
933
1014
  self.class.excel_class
@@ -938,6 +1019,11 @@ module RobustExcelOle
938
1019
  self.class.worksheet_class
939
1020
  end
940
1021
 
1022
+ # @private
1023
+ def address_class
1024
+ self.class.address_class
1025
+ end
1026
+
941
1027
  include MethodHelpers
942
1028
 
943
1029
  private
@@ -12,7 +12,6 @@ module RobustExcelOle
12
12
  class Worksheet < RangeOwners
13
13
 
14
14
  attr_reader :ole_worksheet
15
- attr_reader :workbook
16
15
 
17
16
  def initialize(win32_worksheet)
18
17
  @ole_worksheet = win32_worksheet
@@ -25,7 +24,15 @@ module RobustExcelOle
25
24
  @end_row = last_row
26
25
  @end_column = last_column
27
26
  end
28
- @workbook = workbook_class.new(self.Parent)
27
+ end
28
+
29
+ def workbook
30
+ ole_workbook = self.Parent
31
+ saved_status = ole_workbook.Saved
32
+ ole_workbook.Saved = true unless saved_status
33
+ workbook = workbook_class.new(ole_workbook)
34
+ ole_workbook.Saved = saved_status
35
+ workbook
29
36
  end
30
37
 
31
38
  # sheet name
@@ -33,7 +40,7 @@ module RobustExcelOle
33
40
  def name
34
41
  @ole_worksheet.Name
35
42
  rescue
36
- raise SheetREOError, "name #{name.inspect} could not be determined"
43
+ raise WorksheetREOError, "name #{name.inspect} could not be determined"
37
44
  end
38
45
 
39
46
  # sets sheet name
@@ -190,7 +197,8 @@ module RobustExcelOle
190
197
 
191
198
  # @private
192
199
  def to_s
193
- '#<Worksheet: ' + ('not alive ' unless @workbook.alive?).to_s + name.to_s + " #{File.basename(@workbook.stored_filename)} >"
200
+ '#<Worksheet: ' + name.to_s + ">"
201
+ #'#<Worksheet: ' + ('not alive ' unless workbook.alive?).to_s + name.to_s + " #{File.basename(workbook.stored_filename)} >"
194
202
  end
195
203
 
196
204
  # @private