robust_excel_ole 1.8 → 1.9

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.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