robust_excel_ole 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +1 -0
  2. data/Changelog +15 -0
  3. data/README.rdoc +128 -63
  4. data/README_detail.rdoc +130 -60
  5. data/examples/edit_sheets/example_access_sheets_and_cells.rb +1 -1
  6. data/examples/edit_sheets/example_adding_sheets.rb +2 -2
  7. data/examples/edit_sheets/example_copying.rb +1 -1
  8. data/examples/edit_sheets/example_expanding.rb +1 -1
  9. data/examples/edit_sheets/example_ranges.rb +1 -1
  10. data/examples/edit_sheets/example_saving.rb +2 -2
  11. data/examples/open_save_close/example_control_to_excel.rb +1 -1
  12. data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +2 -2
  13. data/examples/open_save_close/example_if_obstructed_save.rb +2 -2
  14. data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
  15. data/examples/open_save_close/example_if_unsaved_forget.rb +2 -2
  16. data/examples/open_save_close/example_if_unsaved_forget_more.rb +3 -3
  17. data/examples/open_save_close/example_read_only.rb +1 -1
  18. data/examples/open_save_close/example_rename_cells.rb +1 -1
  19. data/examples/open_save_close/example_simple.rb +1 -1
  20. data/examples/open_save_close/example_unobtrusively.rb +3 -3
  21. data/lib/robust_excel_ole.rb +1 -0
  22. data/lib/robust_excel_ole/book.rb +249 -193
  23. data/lib/robust_excel_ole/bookstore.rb +1 -1
  24. data/lib/robust_excel_ole/cell.rb +1 -1
  25. data/lib/robust_excel_ole/excel.rb +125 -4
  26. data/lib/robust_excel_ole/general.rb +1 -92
  27. data/lib/robust_excel_ole/range.rb +1 -1
  28. data/lib/robust_excel_ole/reo_common.rb +37 -0
  29. data/lib/robust_excel_ole/sheet.rb +77 -24
  30. data/lib/robust_excel_ole/version.rb +1 -1
  31. data/spec/book_spec.rb +112 -82
  32. data/spec/book_specs/book_close_spec.rb +44 -1
  33. data/spec/book_specs/book_misc_spec.rb +97 -92
  34. data/spec/book_specs/book_open_spec.rb +40 -8
  35. data/spec/book_specs/book_save_spec.rb +77 -7
  36. data/spec/book_specs/book_sheet_spec.rb +290 -66
  37. data/spec/book_specs/book_unobtr_spec.rb +99 -73
  38. data/spec/bookstore_spec.rb +1 -1
  39. data/spec/cell_spec.rb +2 -2
  40. data/spec/data/another_workbook.xls +0 -0
  41. data/spec/data/workbook.xls +0 -0
  42. data/spec/excel_spec.rb +174 -23
  43. data/spec/general_spec.rb +3 -18
  44. data/spec/range_spec.rb +3 -3
  45. data/spec/reo_common_spec.rb +104 -0
  46. data/spec/sheet_spec.rb +101 -60
  47. metadata +6 -4
@@ -14,7 +14,7 @@ begin
14
14
  simple_save_file = dir + 'workbook_save.xls'
15
15
  File.delete simple_save_file rescue nil
16
16
  book = Book.open(simple_file) # open a book
17
- sheet = book[0] # access a sheet via integer
17
+ sheet = book.sheet(1) # access a sheet via integer
18
18
  cell = sheet[1,1] # access the first cell
19
19
  puts "1st cell: #{cell.Value}" # put the value of the first cell
20
20
  sheet[1,1] = "complex" # write a value into a cell
@@ -33,7 +33,7 @@ begin
33
33
  show_sheets
34
34
 
35
35
  puts "adding a copy of the 2nd sheet"
36
- sheet = @book[1]
36
+ sheet = @book.sheet(2)
37
37
  @book.add_sheet sheet
38
38
  show_sheets
39
39
 
@@ -50,7 +50,7 @@ begin
50
50
  show_sheets
51
51
 
52
52
  puts "adding a copy of the 4th sheet before the 7th sheet and name it 'sheet_copy'"
53
- @book.add_sheet(@book[3], :as => 'sheet_copy', :after => @book[6])
53
+ @book.add_sheet(@book.sheet(4), :as => 'sheet_copy', :after => @book.sheet(7))
54
54
  show_sheets
55
55
 
56
56
  puts"adding a copy of the 2nd sheet and name it again 'second_sheet_copy'"
@@ -41,7 +41,7 @@ begin
41
41
  end
42
42
 
43
43
  sheet_names.each do |sheet_name|
44
- book[sheet_name].Delete()
44
+ book.sheet(sheet_name).Delete()
45
45
  end
46
46
  end
47
47
  end
@@ -42,7 +42,7 @@ begin
42
42
  end
43
43
 
44
44
  sheet_names.each do |sheet_name|
45
- book[sheet_name].Delete()
45
+ book.sheet(sheet_name).Delete()
46
46
  end
47
47
  end
48
48
  end
@@ -14,7 +14,7 @@ begin
14
14
  simple_save_file = dir + 'workbook_save.xls'
15
15
  File.delete simple_save_file rescue nil
16
16
  book = Book.open(simple_file) # open a book
17
- sheet = book['Sheet1'] # access a sheet via the name
17
+ sheet = book.sheet('Sheet1') # access a sheet via the name
18
18
  row_r = sheet.row_range(1) # access the whole range of the first row
19
19
  col_r = sheet.col_range(1, 1..2) # access the first two cells of the range of the first column
20
20
  cell = col_r[0] # access the first cell of these cells
@@ -25,13 +25,13 @@ begin
25
25
  book = Book.open(file_sheet_name)
26
26
  book.add_sheet sheet_orig
27
27
  book.each do |sheet|
28
- sheet.Delete() unless sheet.name == sheet_orig.name
28
+ sheet.Delete unless sheet.name == sheet_orig.name
29
29
  end
30
30
  book.close(:if_unsaved => :save)
31
31
  # alternative: delete all other sheets
32
32
  #book = Book.open(file_sheet_name, :force_excel => :new, :visible => true)
33
33
  #book.each do |sheet|
34
- # book[sheet.Name].Delete() unless sheet.Name == sheet_orig.Name
34
+ # book.sheet(sheet.Name).Delete() unless sheet.Name == sheet_orig.Name
35
35
  #end
36
36
  end
37
37
  end
@@ -14,7 +14,7 @@ begin
14
14
  book = Book.open(file_name) # open a book
15
15
  book.excel.visible = true # make current Excel visible
16
16
  sleep 1
17
- sheet = book[0] # access a sheet
17
+ sheet = book.sheet(1) # access a sheet
18
18
  sheet[1,1] = sheet[1,1].Value == "simple" ? "complex" : "simple" # change a cell
19
19
  sleep 1
20
20
  begin
@@ -14,7 +14,7 @@ begin
14
14
  other_file_name = dir + 'more_data/workbook.xls'
15
15
  book = Book.open(file_name, :visible => true) # open a book, make Excel visible
16
16
  sleep 1
17
- sheet = book[0]
17
+ sheet = book.sheet(1)
18
18
  first_cell = sheet[1,1].Value # access a sheet
19
19
  sheet[1,1] = first_cell == "simple" ? "complex" : "simple" # change a cell
20
20
  sleep 1
@@ -26,7 +26,7 @@ begin
26
26
  book.save # save the unsaved book
27
27
  new_book = Book.open(file_name, :if_obstructed => :close_if_saved) # open the new book, close the saved book
28
28
  sleep 1
29
- new_sheet = new_book[0]
29
+ new_sheet = new_book.sheet(1)
30
30
  new_first_cell = new_sheet[1,1].Value
31
31
  puts "the old book was saved" unless new_first_cell == first_cell
32
32
  new_book.close # close the books
@@ -14,7 +14,7 @@ begin
14
14
  other_file_name = dir + 'more_data/workbook.xls'
15
15
  book = Book.open(file_name, :visible => true) # open a book, make Excel visible
16
16
  sleep 1
17
- sheet = book[0]
17
+ sheet = book.sheet(1)
18
18
  first_cell = sheet[1,1].value # access a sheet
19
19
  sheet[1,1] = first_cell == "simple" ? "complex" : "simple" # change a cell
20
20
  sleep 1
@@ -22,7 +22,7 @@ begin
22
22
  sleep 1 #save the old book, close it, before
23
23
  old_book = Book.open(file_name, :if_obstructed => :forget ,:visible => true) # open the old book
24
24
  sleep 1
25
- old_sheet = old_book[0]
25
+ old_sheet = old_book.sheet(1)
26
26
  old_first_cell = old_sheet[1,1].value
27
27
  puts "the old book was saved" unless old_first_cell == first_cell
28
28
  new_book.close # close the books
@@ -12,7 +12,7 @@ begin
12
12
  dir = create_tmpdir
13
13
  file_name = dir + 'workbook.xls'
14
14
  book = Book.open(file_name) # open a book
15
- sheet = book[0] # access a sheet
15
+ sheet = book.sheet(1) # access a sheet
16
16
  sheet[1,1] = sheet[1,1].value == "simple" ? "complex" : "simple" # change a cell
17
17
  begin
18
18
  new_book = Book.open(file_name) # open another book with the same file name
@@ -14,13 +14,13 @@ begin
14
14
  book = Book.open(file_name) # open a book
15
15
  book.excel.visible = true # make current Excel visible
16
16
  sleep 1
17
- sheet = book[0] # access a sheet
17
+ sheet = book.sheet(1) # access a sheet
18
18
  first_cell = sheet[1,1].value
19
19
  sheet[1,1] = first_cell == "simple" ? "complex" : "simple" # change a cell
20
20
  sleep 1
21
21
  new_book = Book.open(file_name, :if_unsaved => :forget) # open another book with the same file name
22
22
  # and close the unsaved book without saving it
23
- sheet_new_book = new_book[0]
23
+ sheet_new_book = new_book.sheet(1)
24
24
  if (not book.alive?) && new_book.alive? && sheet_new_book[1,1].value == first_cell then # check whether the unsaved book
25
25
  puts "open with :if_unsaved => :forget : the unsaved book is closed and not saved." # is closed and was not saved
26
26
  end
@@ -14,12 +14,12 @@ begin
14
14
  book = Book.open(file_name) # open a book
15
15
  book.excel.visible = true # make current Excel visible
16
16
  sleep
17
- sheet = book[0] # access a sheet
17
+ sheet = book.sheet(1) # access a sheet
18
18
  first_cell = sheet[1,1].value
19
19
  sheet[1,1] = first_cell == "simple" ? "complex" : "simple" # change a cell
20
20
  sleep 1
21
21
  new_book = Book.open(file_name, :if_unsaved => :new_excel, :visible => true) # open another book with the same file name in a new Excel
22
- sheet_new_book = new_book[0]
22
+ sheet_new_book = new_book.sheet(1)
23
23
  if (not book.alive?) && new_book.alive? && sheet_new_book[1,1].value == first_cell then # check whether the unsaved book
24
24
  puts "open with :if_unsaved => :forget : the unsaved book is closed and not saved." # is closed and was not saved
25
25
  end
@@ -28,7 +28,7 @@ begin
28
28
  # open another book in the running Excel application, and make Excel visible, closing the unsaved book
29
29
  another_book = Book.open(file_name, :if_unsaved => :forget, :visible => true)
30
30
  sleep 1
31
- sheet_another_book = another_book[0]
31
+ sheet_another_book = another_book.sheet(1)
32
32
  sheet_another_book[1,1] = sheet_another_book[1,1].value == "simple" ? "complex" : "simple" # change a cell
33
33
  another_book.close(:if_unsaved => :forget ) # close the last book without saving it.
34
34
  book.close(:if_unsaved => :save) # close the first book and save it before
@@ -12,7 +12,7 @@ begin
12
12
  file_name = dir + 'workbook.xls'
13
13
  other_file_name = dir + 'different_workbook.xls'
14
14
  book = Book.open(file_name, :read_only => true, :visible => true) # open a book with read_only and make Excel visible
15
- sheet = book[0] # access a sheet
15
+ sheet = book.sheet(1) # access a sheet
16
16
  sleep 1
17
17
  sheet[1,1] = sheet[1,1].value == "simple" ? "complex" : "simple" # change a cell
18
18
  sleep 1
@@ -13,7 +13,7 @@ begin
13
13
  file_name = dir + 'workbook.xls'
14
14
  book = Book.open(file_name) # open a book. default: :read_only => false
15
15
  book.excel.visible = true # make current Excel visible
16
- sheet = book[0]
16
+ sheet = book.sheet(1)
17
17
  workbook = book.ole_workbook
18
18
  fullname = workbook.Fullname
19
19
  puts "fullname: #{fullname}"
@@ -19,7 +19,7 @@ begin
19
19
  other_file_name = dir + 'different_workbook.xls'
20
20
  book = Book.open(file_name) # open a book. default: :read_only => false
21
21
  book.excel.visible = true # make current Excel visible
22
- sheet = book[0] # access a sheet
22
+ sheet = book.sheet(1) # access a sheet
23
23
  sleep 1
24
24
  sheet[1,1] = sheet[1,1].value == "simple" ? "complex" : "simple" # change a cell
25
25
  sleep 1
@@ -11,14 +11,14 @@ begin
11
11
  dir = create_tmpdir
12
12
  simple_file = dir + 'workbook.xls'
13
13
  book = Book.open(simple_file, :visible => true) # open a book, make Excel visible
14
- old_sheet = book[0]
14
+ old_sheet = book.sheet(1)
15
15
  p "1st cell: #{old_sheet[1,1].value}"
16
16
  sleep 2
17
17
  Book.unobtrusively(simple_file) do |book| # modify the book and keep its status unchanged
18
- sheet = book[0]
18
+ sheet = book.sheet(1)
19
19
  sheet[1,1] = sheet[1,1].value == "simple" ? "complex" : "simple"
20
20
  end
21
- new_sheet = book[0]
21
+ new_sheet = book.sheet(1)
22
22
  p "1st cell: #{new_sheet[1,1].value}"
23
23
  p "book saved" if book.Saved
24
24
  book.close # close the book
@@ -1,4 +1,5 @@
1
1
  require "win32ole"
2
+ require File.join(File.dirname(__FILE__), 'robust_excel_ole/reo_common')
2
3
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/general')
3
4
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/excel')
4
5
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/bookstore')
@@ -4,7 +4,7 @@ require 'weakref'
4
4
 
5
5
  module RobustExcelOle
6
6
 
7
- class Book
7
+ class Book < REOCommon
8
8
 
9
9
  attr_accessor :excel
10
10
  attr_accessor :ole_workbook
@@ -21,7 +21,8 @@ module RobustExcelOle
21
21
  :if_unsaved => :raise,
22
22
  :if_obstructed => :raise,
23
23
  :if_absent => :raise,
24
- :read_only => false
24
+ :read_only => false,
25
+ :check_compatibility => true
25
26
  }
26
27
 
27
28
  class << self
@@ -31,7 +32,7 @@ module RobustExcelOle
31
32
  # @param [Hash] opts the options
32
33
  # @option opts [Variant] :default_excel :reuse (default), :new, or <excel-instance>
33
34
  # @option opts [Variant] :force_excel :new (default), or <excel-instance>
34
- # @option opts [Symbol] :if_unsaved :raise (default), :forget, :accept, :alert, or :new_excel
35
+ # @option opts [Symbol] :if_unsaved :raise (default), :forget, :accept, :alert, :excel, or :new_excel
35
36
  # @option opts [Symbol] :if_obstructed :raise (default), :forget, :save, :close_if_saved, or _new_excel
36
37
  # @option opts [Symbol] :if_absent :raise (default), or :create
37
38
  # @option opts [Boolean] :read_only true (default), or false
@@ -52,7 +53,7 @@ module RobustExcelOle
52
53
  # :raise -> raises an exception
53
54
  # :forget -> close the unsaved workbook, open the new workbook
54
55
  # :accept -> lets the unsaved workbook open
55
- # :alert -> gives control to Excel
56
+ # :alert or :excel -> gives control to Excel
56
57
  # :new_excel -> opens the new workbook in a new Excel instance
57
58
  # :if_obstructed if a workbook with the same name in a different path is open, then
58
59
  # :raise -> raises an exception
@@ -66,7 +67,8 @@ module RobustExcelOle
66
67
  #
67
68
  # :read_only opens in read-only mode
68
69
  # :displayalerts enables DisplayAlerts in Excel
69
- # :visible makes visible in Excel
70
+ # :visible makes visible in Excel
71
+ # :check_compatibility check compatibility when saving
70
72
  # if :default_excel is set, then DisplayAlerts and Visible are set only if these parameters are given
71
73
  # @return [Book] a workbook
72
74
  def open(file, opts={ }, &block)
@@ -114,7 +116,7 @@ module RobustExcelOle
114
116
  end
115
117
 
116
118
  # creates a new Book object, if a file name is given
117
- # lifts the workbook to a Book object, if a workbook is given
119
+ # Promotes the workbook to a Book object, if a win32ole-workbook is given
118
120
  # @param [Variant] file_or_workbook file name or workbook
119
121
  # @param [Hash] opts the options
120
122
  # @option opts [Symbol] see above
@@ -165,7 +167,11 @@ module RobustExcelOle
165
167
  object.excel
166
168
  end
167
169
  else
168
- object.excel
170
+ begin
171
+ object.excel
172
+ rescue
173
+ raise ExcelErrorOpen, "given object is neither an Excel, a Book, nor a Win32ole"
174
+ end
169
175
  end
170
176
  #rescue
171
177
  # trace "no Excel, Book, or WIN32OLE object representing a Workbook or an Excel instance"
@@ -215,7 +221,7 @@ module RobustExcelOle
215
221
  if obstructed_by_other_book then
216
222
  case options[:if_obstructed]
217
223
  when :raise
218
- raise ExcelErrorOpen, "blocked by a book with the same name in a different path: #{File.basename(file).inspect}"
224
+ raise ExcelErrorOpen, "blocked by a book with the same name in a different path: #{@ole_workbook.Fullname.tr('\\','/')}"
219
225
  when :forget
220
226
  @ole_workbook.Close
221
227
  @ole_workbook = nil
@@ -227,7 +233,7 @@ module RobustExcelOle
227
233
  open_or_create_workbook(file, options)
228
234
  when :close_if_saved
229
235
  if (not @ole_workbook.Saved) then
230
- raise ExcelErrorOpen, "workbook with the same name in a different path is unsaved: #{File.basename(file).inspect}"
236
+ raise ExcelErrorOpen, "workbook with the same name in a different path is unsaved: #{@ole_workbook.Fullname.tr('\\','/')}"
231
237
  else
232
238
  @ole_workbook.Close
233
239
  @ole_workbook = nil
@@ -253,7 +259,7 @@ module RobustExcelOle
253
259
  open_or_create_workbook(file, options)
254
260
  when :accept
255
261
  # do nothing
256
- when :alert
262
+ when :alert, :excel
257
263
  @excel.with_displayalerts true do
258
264
  open_or_create_workbook(file,options)
259
265
  end
@@ -299,7 +305,8 @@ module RobustExcelOle
299
305
  workbooks.Add if @excel.Version == "12.0" && count == 0
300
306
  workbooks.Open(filename,{ 'ReadOnly' => options[:read_only] })
301
307
  workbooks.Item(1).Close if @excel.Version == "12.0" && count == 0
302
- @can_be_closed = false
308
+ workbooks.Item(1).CheckCompatibility = options[:check_compatibility]
309
+ @can_be_closed = false if @can_be_closed.nil?
303
310
  rescue WIN32OLERuntimeError => msg
304
311
  trace "WIN32OLERuntimeError: #{msg.message}"
305
312
  if msg.message =~ /800A03EC/
@@ -329,7 +336,7 @@ module RobustExcelOle
329
336
  # :save -> saves the workbook before it is closed
330
337
  # :forget -> closes the workbook
331
338
  # :keep_open -> keep the workbook open
332
- # :alert -> gives control to excel
339
+ # :alert or :excel -> gives control to excel
333
340
  # @raise ExcelErrorClose if the option :if_unsaved is :raise and the workbook is unsaved, or option is invalid
334
341
  # @raise ExcelErrorCanceled if the user has canceled
335
342
  def close(opts = {:if_unsaved => :raise})
@@ -344,7 +351,7 @@ module RobustExcelOle
344
351
  close_workbook
345
352
  when :keep_open
346
353
  # nothing
347
- when :alert
354
+ when :alert, :excel
348
355
  @excel.with_displayalerts true do
349
356
  close_workbook
350
357
  end
@@ -354,7 +361,8 @@ module RobustExcelOle
354
361
  else
355
362
  close_workbook
356
363
  end
357
- raise ExcelUserCanceled, "close: canceled by user" if alive? && opts[:if_unsaved] == :alert && (not @ole_workbook.Saved)
364
+ raise ExcelUserCanceled, "close: canceled by user" if alive? &&
365
+ (opts[:if_unsaved] == :alert || opts[:if_unsaved] == :excel) && (not @ole_workbook.Saved)
358
366
  end
359
367
 
360
368
  private
@@ -403,8 +411,9 @@ module RobustExcelOle
403
411
  # true: closes it and open it as writable in the Excel instance where it was open so far
404
412
  # false (default) opens it as writable in another running excel instance, if it exists,
405
413
  # otherwise open in a new Excel instance.
406
- # :displayalerts enables DisplayAlerts in Excel
407
- # :visible makes visible in Excel
414
+ # :displayalerts enables DisplayAlerts in Excel
415
+ # :visible makes visible in Excel
416
+ # :check_compatibility checks compatibility when saving
408
417
  # @return [Book] a workbook
409
418
  def self.unobtrusively(file, if_closed = nil, opts = { }, &block)
410
419
  if if_closed.is_a? Hash
@@ -415,7 +424,8 @@ module RobustExcelOle
415
424
  options = {
416
425
  :read_only => false,
417
426
  :readonly_excel => false,
418
- :keep_open => false
427
+ :keep_open => false,
428
+ :check_compatibility => true
419
429
  }.merge(opts)
420
430
  book = bookstore.fetch(file, :prefer_writable => (not options[:read_only]))
421
431
  was_not_alive_or_nil = book.nil? || (not book.alive?)
@@ -428,7 +438,7 @@ module RobustExcelOle
428
438
  false
429
439
  end
430
440
  was_saved = was_not_alive_or_nil ? true : book.saved
431
- was_writable = book.writable unless was_not_alive_or_nil
441
+ was_writable = book.writable unless was_not_alive_or_nil
432
442
  begin
433
443
  book =
434
444
  if was_not_alive_or_nil
@@ -450,15 +460,19 @@ module RobustExcelOle
450
460
  end
451
461
  book.excel.displayalerts = options[:displayalerts] unless options[:displayalerts].nil?
452
462
  book.excel.visible = options[:visible] unless options[:visible].nil?
463
+ old_check_compatibility = book.CheckCompatibility
464
+ book.CheckCompatibility = options[:check_compatibility]
453
465
  yield book
454
466
  ensure
455
- book.save if (was_not_alive_or_nil || was_saved || ((not options[:read_only]) && (not was_writable))) && (not options[:read_only]) && book && (not book.saved)
467
+ was_saved_or_appeared = was_saved || was_not_alive_or_nil || (not was_writable)
468
+ book.save if book && (not book.saved) && (not options[:read_only]) && was_saved_or_appeared
456
469
  # book was open, readonly and shoud be modified
457
470
  if (not was_not_alive_or_nil) && (not options[:read_only]) && (not was_writable) && options[:readonly_excel]
458
471
  open(file, :force_excel => book.excel, :if_obstructed => :new_excel, :read_only => true)
459
472
  end
460
473
  @can_be_closed = true if options[:keep_open] && book
461
474
  book.close if (was_not_alive_or_nil && (not now_alive) && (not options[:keep_open]) && book)
475
+ book.CheckCompatibility = old_check_compatibility if book && book.alive?
462
476
  end
463
477
  end
464
478
 
@@ -467,136 +481,6 @@ module RobustExcelOle
467
481
  self.class.open(self.stored_filename)
468
482
  end
469
483
 
470
- # renames a range
471
- # @param [String] name the previous range name
472
- # @param [String] new_name the new range name
473
- # @raise ExcelError if name is not in the file, or if new_name cannot be set
474
- def rename_range(name, new_name)
475
- begin
476
- item = self.Names.Item(name)
477
- rescue WIN32OLERuntimeError
478
- raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
479
- end
480
- begin
481
- item.Name = new_name
482
- rescue WIN32OLERuntimeError
483
- raise ExcelError, "name error in #{File.basename(self.stored_filename).inspect}"
484
- end
485
- end
486
-
487
- # returns the contents of a range with given name
488
- # @param [String] name the range name
489
- # @param [Hash] opts the options
490
- # @option opts [Symbol] :default the default value that is provided if no contents could be returned
491
- # @raise ExcelError if range name is not in the workbook
492
- # @raise SheetError if range value could not be evaluated
493
- # @return [Variant] the contents of a range with given name
494
- # if no contents could be returned, then return default value, if a default value was provided
495
- # raise an error, otherwise
496
- def nvalue(name, opts = {:default => nil})
497
- begin
498
- item = self.Names.Item(name)
499
- rescue WIN32OLERuntimeError
500
- return opts[:default] if opts[:default]
501
- raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
502
- end
503
- begin
504
- value = item.RefersToRange.Value
505
- rescue WIN32OLERuntimeError
506
- begin
507
- sheet = self[0]
508
- value = sheet.Evaluate(name)
509
- rescue WIN32OLERuntimeError
510
- return opts[:default] if opts[:default]
511
- raise SheetError, "cannot evaluate name #{name.inspect} in sheet"
512
- end
513
- end
514
- if value == -2146826259
515
- return opts[:default] if opts[:default]
516
- raise SheetError, "cannot evaluate name #{name.inspect} in sheet"
517
- end
518
- return opts[:default] if (value.nil? && opts[:default])
519
- value
520
- end
521
-
522
- # sets the contents of a range with given name
523
- # @param [String] name the range name
524
- # @param [Variant] value the contents of the range
525
- # @raise ExcelError if range name is not in the workbook or if a RefersToRange error occurs
526
- def set_nvalue(name, value)
527
- begin
528
- item = self.Names.Item(name)
529
- rescue WIN32OLERuntimeError
530
- raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
531
- end
532
- begin
533
- item.RefersToRange.Value = value
534
- rescue WIN32OLERuntimeError
535
- raise ExcelError, "RefersToRange error of name #{name.inspect} in #{File.basename(self.stored_filename).inspect}"
536
- end
537
- end
538
-
539
- # brings workbook to foreground, makes it available for heyboard inputs, makes the Excel instance visible
540
- # @raise ExcelError if workbook cannot be activated
541
- def activate
542
- @excel.visible = true
543
- begin
544
- Win32API.new("user32","SetForegroundWindow","I","I").call(@excel.hwnd) # Excel 2010
545
- @ole_workbook.Activate # Excel 2007
546
- rescue WIN32OLERuntimeError
547
- raise ExcelError, "cannot activate"
548
- end
549
- end
550
-
551
- # returns true, if the workbook is visible, false otherwise
552
- def visible
553
- @excel.Windows(@ole_workbook.Name).Visible
554
- end
555
-
556
- # makes a workbook visible or invisible
557
- # @param [Boolean] visible_value value that determines whether the workbook shall be visible
558
- def visible= visible_value
559
- saved = @ole_workbook.Saved
560
- @excel.Windows(@ole_workbook.Name).Visible = visible_value
561
- save if saved
562
- end
563
-
564
- # returns true, if the workbook reacts to methods, false otherwise
565
- def alive?
566
- begin
567
- @ole_workbook.Name
568
- true
569
- rescue
570
- @ole_workbook = nil # dead object won't be alive again
571
- #t $!.message
572
- false
573
- end
574
- end
575
-
576
- # returns the full file name of the workbook
577
- def filename
578
- @ole_workbook.Fullname.tr('\\','/') rescue nil
579
- end
580
-
581
- def writable # :nodoc: #
582
- (not @ole_workbook.ReadOnly) if @ole_workbook
583
- end
584
-
585
- def saved # :nodoc: #
586
- @ole_workbook.Saved if @ole_workbook
587
- end
588
-
589
- # @return [Boolean] true, if the full book names and excel Instances are identical, false otherwise
590
- def == other_book
591
- other_book.is_a?(Book) &&
592
- @excel == other_book.excel &&
593
- self.filename == other_book.filename
594
- end
595
-
596
- def self.books
597
- bookstore.books
598
- end
599
-
600
484
  # simple save of a workbook.
601
485
  # @raise ExcelErrorSave if workbook is not alive or opened for read-only, or another error occurs
602
486
  # @return [Boolean] true, if successfully saved, nil otherwise
@@ -618,13 +502,13 @@ module RobustExcelOle
618
502
  # saves a workbook with a given file name.
619
503
  # @param [String] file file name
620
504
  # @param [Hash] opts the options
621
- # @option opts [Symbol] :if_exists :raise (default), :overwrite, or :alert
505
+ # @option opts [Symbol] :if_exists :raise (default), :overwrite, or :alert, :excel
622
506
  # @option opts [Symbol] :if_obstructed :raise (default), :forget, :save, or :close_if_saved
623
507
  # options:
624
508
  # :if_exists if a file with the same name exists, then
625
509
  # :raise -> raises an exception, dont't write the file (default)
626
510
  # :overwrite -> writes the file, delete the old file
627
- # :alert -> gives control to Excel
511
+ # :alert or :excel -> gives control to Excel
628
512
  # :if_obstructed if a workbook with the same name and different path is already open and blocks the saving, then
629
513
  # :raise -> raises an exception
630
514
  # :forget -> closes the blocking workbook
@@ -634,7 +518,7 @@ module RobustExcelOle
634
518
  # @raise ExcelErrorSave if workbook is not alive, opened in read-only mode, invalid options,
635
519
  # the file already exists (with option :if_exists :raise),
636
520
  # the workbook is blocked by another one (with option :if_obstructed :raise)
637
- # @return [Boolean] true, if successfully saved, nil otherwise
521
+ # @return [Book], the book itself, if successfully saved, raises an exception otherwise
638
522
  def save_as(file = nil, opts = { } )
639
523
  raise ExcelErrorSave, "Workbook is not alive" if (not alive?)
640
524
  raise ExcelErrorSave, "Not opened for writing (opened with :read_only option)" if @ole_workbook.ReadOnly
@@ -647,7 +531,7 @@ module RobustExcelOle
647
531
  when :overwrite
648
532
  if file == self.filename
649
533
  save
650
- return
534
+ return self
651
535
  else
652
536
  begin
653
537
  File.delete(file)
@@ -655,12 +539,11 @@ module RobustExcelOle
655
539
  raise ExcelErrorSave, "workbook is open and used in Excel"
656
540
  end
657
541
  end
658
- when :alert
542
+ when :alert, :excel
659
543
  @excel.with_displayalerts true do
660
544
  save_as_workbook(file, options)
661
545
  end
662
- true
663
- return
546
+ return self
664
547
  when :raise
665
548
  raise ExcelErrorSave, "file already exists: #{File.basename(file).inspect}"
666
549
  else
@@ -676,7 +559,7 @@ module RobustExcelOle
676
559
  if blocking_workbook then
677
560
  case options[:if_obstructed]
678
561
  when :raise
679
- raise ExcelErrorSave, "blocked by another workbook: #{File.basename(file).inspect}"
562
+ raise ExcelErrorSave, "blocked by another workbook: #{blocking_workbook.Fullname.tr('\\','/')}"
680
563
  when :forget
681
564
  # nothing
682
565
  when :save
@@ -689,7 +572,7 @@ module RobustExcelOle
689
572
  blocking_workbook.Close
690
573
  end
691
574
  save_as_workbook(file, options)
692
- true
575
+ self
693
576
  end
694
577
 
695
578
  private
@@ -707,7 +590,7 @@ module RobustExcelOle
707
590
  bookstore.store(self)
708
591
  rescue WIN32OLERuntimeError => msg
709
592
  if msg.message =~ /SaveAs/ and msg.message =~ /Workbook/ then
710
- if options[:if_exists] == :alert then
593
+ if options[:if_exists] == :alert || options[:if_exists] == :excel then
711
594
  raise ExcelErrorSave, "not saved or canceled by user"
712
595
  else
713
596
  return nil
@@ -722,65 +605,238 @@ module RobustExcelOle
722
605
  public
723
606
 
724
607
  # returns a sheet, if a sheet name or a number is given
725
- # returns the value of the range, if a global name of a range in the book is given
726
- def [] name
727
- name += 1 if name.is_a? Numeric
608
+ # @param [String] or [Number]
609
+ # @returns [Sheet]
610
+ def sheet(name)
728
611
  begin
729
612
  sheet_class.new(@ole_workbook.Worksheets.Item(name))
730
613
  rescue WIN32OLERuntimeError => msg
731
- if msg.message =~ /8002000B/
732
- nvalue(name)
733
- else
734
- raise ExcelError, "could neither return a sheet nor a value of a range when giving the name #{name.inspect}"
735
- end
614
+ raise ExcelError, "could not return a sheet with name #{name.inspect}"
615
+ trace "#{msg.message}"
736
616
  end
737
- end
738
-
739
- # sets the value of a range given its name
740
- # @param [String] name the name of the range
741
- # @param [Variant] value the contents of the range
742
- def []= (name, value)
743
- set_nvalue(name,value)
744
- end
617
+ end
745
618
 
746
619
  def each
747
620
  @ole_workbook.Worksheets.each do |sheet|
748
621
  yield sheet_class.new(sheet)
749
622
  end
750
623
  end
624
+
625
+ # copies a sheet to another position
626
+ # default: copied sheet is appended
627
+ # @param [Sheet] sheet a sheet that shall be copied
628
+ # @param [Hash] opts the options
629
+ # @option opts [Symbol] :as new name of the copied sheet
630
+ # @option opts [Symbol] :before a sheet before which the sheet shall be inserted
631
+ # @option opts [Symbol] :after a sheet after which the sheet shall be inserted
632
+ # @raise ExcelErrorSheet if the sheet name already exists
633
+ # @return [Sheet] the copied sheet
634
+ def copy_sheet(sheet, opts = { })
635
+ new_sheet_name = opts.delete(:as)
636
+ after_or_before, base_sheet = opts.to_a.first || [:after, last_sheet]
637
+ sheet.Copy({ after_or_before.to_s => base_sheet.worksheet })
638
+ new_sheet = sheet_class.new(@excel.Activesheet)
639
+ begin
640
+ new_sheet.name = new_sheet_name if new_sheet_name
641
+ rescue WIN32OLERuntimeError => msg
642
+ msg.message =~ /800A03EC/ ? raise(ExcelErrorSheet, "sheet name already exists") : raise(ExcelErrorSheetUnknown)
643
+ end
644
+ new_sheet
645
+ end
751
646
 
752
- # adds a sheet to the workbook
753
- # @param [Sheet] sheet a sheet
647
+ # adds an empty sheet
648
+ # default: empty sheet is appended
754
649
  # @param [Hash] opts the options
755
- # @option opts [Symbol] :as new name of the copyed sheet
650
+ # @option opts [Symbol] :as new name of the copied added sheet
756
651
  # @option opts [Symbol] :before a sheet before which the sheet shall be inserted
757
652
  # @option opts [Symbol] :after a sheet after which the sheet shall be inserted
758
653
  # @raise ExcelErrorSheet if the sheet name already exists
759
654
  # @return [Sheet] the added sheet
760
- def add_sheet(sheet = nil, opts = { })
761
- if sheet.is_a? Hash
762
- opts = sheet
763
- sheet = nil
764
- end
655
+ def add_empty_sheet(opts = { })
765
656
  new_sheet_name = opts.delete(:as)
766
- ws = @ole_workbook.Worksheets
767
- after_or_before, base_sheet = opts.to_a.first || [:after, sheet_class.new(ws.Item(ws.Count))]
768
- base_sheet = base_sheet.worksheet
769
- sheet ? sheet.Copy({ after_or_before.to_s => base_sheet }) : @ole_workbook.WorkSheets.Add({ after_or_before.to_s => base_sheet })
657
+ after_or_before, base_sheet = opts.to_a.first || [:after, last_sheet]
658
+ @ole_workbook.Worksheets.Add({ after_or_before.to_s => base_sheet.worksheet })
770
659
  new_sheet = sheet_class.new(@excel.Activesheet)
771
660
  begin
772
661
  new_sheet.name = new_sheet_name if new_sheet_name
773
662
  rescue WIN32OLERuntimeError => msg
774
- if msg.message =~ /800A03EC/
775
- raise ExcelErrorSheet, "sheet name already exists"
776
- else
777
- trace "#{msg.message}"
778
- raise ExcelErrorSheetUnknown
779
- end
663
+ msg.message =~ /800A03EC/ ? raise(ExcelErrorSheet, "sheet name already exists") : raise(ExcelErrorSheetUnknown)
780
664
  end
781
665
  new_sheet
666
+ end
667
+
668
+ # copies a sheet to another position if a sheet is given, or adds an empty sheet
669
+ # default: copied or empty sheet is appended, i.e. added behind the last sheet
670
+ # @param [Sheet] sheet a sheet that shall be copied (optional)
671
+ # @param [Hash] opts the options
672
+ # @option opts [Symbol] :as new name of the copied or added sheet
673
+ # @option opts [Symbol] :before a sheet before which the sheet shall be inserted
674
+ # @option opts [Symbol] :after a sheet after which the sheet shall be inserted
675
+ # @raise ExcelErrorSheet if the sheet name already exists
676
+ # @return [Sheet] the copied or added sheet
677
+ def add_or_copy_sheet(sheet = nil, opts = { })
678
+ if sheet.is_a? Hash
679
+ opts = sheet
680
+ sheet = nil
681
+ end
682
+ sheet ? copy_sheet(sheet, opts) : add_empty_sheet(opts)
782
683
  end
783
684
 
685
+ # for compatibility to older versions
686
+ def add_sheet(sheet = nil, opts = { })
687
+ add_or_copy_sheet(sheet, opts)
688
+ end
689
+
690
+ def last_sheet
691
+ sheet_class.new(@ole_workbook.Worksheets.Item(@ole_workbook.Worksheets.Count))
692
+ end
693
+
694
+ def first_sheet
695
+ sheet_class.new(@ole_workbook.Worksheets.Item(1))
696
+ end
697
+
698
+ # returns the value of a range
699
+ # @param [String] name the name of a range
700
+ # @returns [Variant] the value of the range
701
+ def [] name
702
+ nameval(name)
703
+ end
704
+
705
+ # sets the value of a range
706
+ # @param [String] name the name of the range
707
+ # @param [Variant] value the contents of the range
708
+ def []= (name, value)
709
+ set_nameval(name,value)
710
+ end
711
+
712
+ # returns the contents of a range with given name
713
+ # evaluates formula contents of the range is a formula
714
+ # if no contents could be returned, then return default value, if provided, raise error otherwise
715
+ # @param [String] name the name of the range
716
+ # @param [Hash] opts the options
717
+ # @option opts [Symbol] :default the default value that is provided if no contents could be returned
718
+ # @raise ExcelError if range name is not in the workbook or if range value could not be evaluated
719
+ # @return [Variant] the contents of a range with given name
720
+ def nameval(name, opts = {:default => nil})
721
+ begin
722
+ name_obj = self.Names.Item(name)
723
+ rescue WIN32OLERuntimeError
724
+ return opts[:default] if opts[:default]
725
+ raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
726
+ end
727
+ begin
728
+ value = name_obj.RefersToRange.Value
729
+ rescue WIN32OLERuntimeError
730
+ begin
731
+ value = self.sheet(1).Evaluate(name_obj.Name)
732
+ rescue WIN32OLERuntimeError
733
+ return opts[:default] if opts[:default]
734
+ raise SheetError, "cannot evaluate name #{name.inspect} in #{File.basename(self.stored_filename).inspect}"
735
+ end
736
+ end
737
+ if value == -2146826259
738
+ return opts[:default] if opts[:default]
739
+ raise SheetError, "cannot evaluate name #{name.inspect} in #{File.basename(self.stored_filename).inspect}"
740
+ end
741
+ return opts[:default] if (value.nil? && opts[:default])
742
+ value
743
+ end
744
+
745
+ # sets the contents of a range
746
+ # @param [String] name the name of a range
747
+ # @param [Variant] value the contents of the range
748
+ # @raise ExcelError if range name is not in the workbook or if value could not be assigned to range
749
+ def set_nameval(name, value)
750
+ begin
751
+ name_obj = self.Names.Item(name)
752
+ rescue WIN32OLERuntimeError
753
+ raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
754
+ end
755
+ begin
756
+ name_obj.RefersToRange.Value = value
757
+ rescue WIN32OLERuntimeError
758
+ raise ExcelError, "cannot assign value to range named #{name.inspect} in #{File.basename(self.stored_filename).inspect}"
759
+ end
760
+ end
761
+
762
+ # renames a range
763
+ # @param [String] name the previous range name
764
+ # @param [String] new_name the new range name
765
+ # @raise ExcelError if name is not in the file, or if new_name cannot be set
766
+ def rename_range(name, new_name)
767
+ begin
768
+ item = self.Names.Item(name)
769
+ rescue WIN32OLERuntimeError
770
+ raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
771
+ end
772
+ begin
773
+ item.Name = new_name
774
+ rescue WIN32OLERuntimeError
775
+ raise ExcelError, "name error in #{File.basename(self.stored_filename).inspect}"
776
+ end
777
+ end
778
+
779
+ # brings workbook to foreground, makes it available for heyboard inputs, makes the Excel instance visible
780
+ # @raise ExcelError if workbook cannot be activated
781
+ def activate
782
+ @excel.visible = true
783
+ begin
784
+ Win32API.new("user32","SetForegroundWindow","I","I").call(@excel.hwnd) # Excel 2010
785
+ @ole_workbook.Activate # Excel 2007
786
+ rescue WIN32OLERuntimeError
787
+ raise ExcelError, "cannot activate"
788
+ end
789
+ end
790
+
791
+ # returns true, if the workbook is visible, false otherwise
792
+ def visible
793
+ @excel.Windows(@ole_workbook.Name).Visible
794
+ end
795
+
796
+ # makes a workbook visible or invisible
797
+ # @param [Boolean] visible_value value that determines whether the workbook shall be visible
798
+ def visible= visible_value
799
+ saved = @ole_workbook.Saved
800
+ @excel.Windows(@ole_workbook.Name).Visible = visible_value
801
+ save if saved
802
+ end
803
+
804
+ # returns true, if the workbook reacts to methods, false otherwise
805
+ def alive?
806
+ begin
807
+ @ole_workbook.Name
808
+ true
809
+ rescue
810
+ @ole_workbook = nil # dead object won't be alive again
811
+ #t $!.message
812
+ false
813
+ end
814
+ end
815
+
816
+ # returns the full file name of the workbook
817
+ def filename
818
+ @ole_workbook.Fullname.tr('\\','/') rescue nil
819
+ end
820
+
821
+ def writable # :nodoc: #
822
+ (not @ole_workbook.ReadOnly) if @ole_workbook
823
+ end
824
+
825
+ def saved # :nodoc: #
826
+ @ole_workbook.Saved if @ole_workbook
827
+ end
828
+
829
+ # @return [Boolean] true, if the full book names and excel Instances are identical, false otherwise
830
+ def == other_book
831
+ other_book.is_a?(Book) &&
832
+ @excel == other_book.excel &&
833
+ self.filename == other_book.filename
834
+ end
835
+
836
+ def self.books
837
+ bookstore.books
838
+ end
839
+
784
840
  def self.bookstore # :nodoc: #
785
841
  @@bookstore ||= Bookstore.new
786
842
  end