robust_excel_ole 1.10 → 1.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9645dff34f91fdb38da9b5a8375e17170bace4159355b126b142cb4ab6689bf
4
- data.tar.gz: d0ca3dbd87bca685ecb98ba1ac747b427c430e52bfe3de5b14beccfefd30d94c
3
+ metadata.gz: 344c336ba61c5f1b98839047f69cbbac1ddef451291fa32260c47218421d78cc
4
+ data.tar.gz: 169a63ca7cb3d582443aa046228797cc946e9f47f45e97019b21504f8a897384
5
5
  SHA512:
6
- metadata.gz: ae0be8a8538c3dd3c27a44c3d11d781be260cb2898e1cf221925d0902160ef4104b9142fd99c106ee5f210aa83688fa062d4ee1a13c8ae49dcb96b150b3baaeb
7
- data.tar.gz: c21be14de79476461692a475685e518f478947051ac9cc8d417952646690b642e8dc2e52999a16b56c2cb8e337ac58b21ae2a28dfffbd8f89260aeced27d216b
6
+ metadata.gz: dc9e189d01012805dd02d668c833e96b9597997b518a2b9ce6beb671969e007d8cae5d362a045fbd439a4368cf7ddfdfee66b535d60e9b4e9c68db7a99023989
7
+ data.tar.gz: 1e04fa12bc2e5956fb0efa68a92b185557b8180909da820517ab55affd3bf8154d297aa9a2b0eaa8e8a161fc31972560ea74f5beefa2fc51225f45b392c122fc
data/Changelog CHANGED
@@ -1,6 +1,14 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+
5
+ ## [1.11]
6
+
7
+ ### Added
8
+ - Range#==, Worksheet#==
9
+ - Workbook#open: options :if_blocked is synonym to :if_obstructed
10
+ - Simplified Code
11
+
4
12
  ## [1.10]
5
13
 
6
14
  ### Added
data/README.rdoc CHANGED
@@ -9,7 +9,7 @@ RobustExcelOle works by sending VBA methods via Win32OLE. It keeps track of Exce
9
9
 
10
10
  == Requirements
11
11
 
12
- * Ruby 1.8.6 or higher
12
+ Ruby 2.1 or higher. Most functions run on Ruby 1.8.6, too.
13
13
 
14
14
  == Installation
15
15
 
@@ -134,6 +134,8 @@ or, using abbreviations,
134
134
 
135
135
  workbook = Workbook.open('spec/data/workbook.xls', :v => true)
136
136
 
137
+ Note that Workbook.open can also connect to workbooks which were not opened via RobustExcelOle (but had been opened before by some user).
138
+
137
139
  For more details about opening workbooks see {README_open}[https://github.com/Thomas008/robust_excel_ole/blob/master/docs/README_open.rdoc]
138
140
 
139
141
  We can do a simple save
@@ -234,7 +236,7 @@ Similarly, if the user has opened a workbook that has the same name but a differ
234
236
 
235
237
  workbook1 = Workbook.open('spec/data/workbook.xls')
236
238
  # do something
237
- workbook2 = Workbook.open('spec/data/more/workbook.xls', :if_obstructed => :forget)
239
+ workbook2 = Workbook.open('spec/data/more/workbook.xls', :if_blocked => :forget)
238
240
 
239
241
  For more details about opening and closing workbooks in Excel instances see {README_open}[https://github.com/Thomas008/robust_excel_ole/blob/master/docs/README_open.rdoc]
240
242
 
@@ -309,7 +311,7 @@ For more details about reading and writing contents of cells and ranges see {REA
309
311
 
310
312
  RobustExcelOle runs in jruby as well. You can call the console via the command +jreo+. However, note that, due to some restrictions of the library jruby-win32ole, there are some restrictions. So we had implemented some adaptions and workarounds. Some issues still remain when running under jruby:
311
313
 
312
- 1. Excel.current or Excel.new(:reuse => true) does not connect to an already running Excel instance, unless some workbook has been opened via RobustExcelOle. 2. Excel.kill_all does work, Excel.close_all does not work. 3. When providing an absolute path, you have to state the "/" ("C:/abc.xls" works, "C:abc" does not work)
314
+ 1. Excel.current or Excel.new(:reuse => true) does not connect to an already running Excel instance, unless some workbook has been opened via RobustExcelOle. 2. Excel.kill_all does work, Excel.close_all does not work. 3. When providing an absolute path, you have to state the "/" ("C:/abc.xls" works, "C:abc" does not work).
313
315
 
314
316
 
315
317
 
@@ -329,7 +331,7 @@ You can run the examples included in the directory +examples+, e.g.
329
331
 
330
332
  This project RobustExcelOle is work in progress. We are happy to implement further features. So we invite you to send your pull requests. We then strive to realize them as soon as possible. If you have any feedback, or you find use cases that RobustExcelOle does not satisfy, please let us know.
331
333
 
332
- RobustExcelOle is being tested for Excel 2010. It can be used for any recent Excel Office version.
334
+ RobustExcelOle is being tested for Excel 2010 and Excel 2013. It can be used for any recent Excel Office version. Most functions should run on Excel 2007 as well.
333
335
 
334
336
  RobustExcelOle has been optimised with help of the rubocop and the rcov tool.
335
337
 
@@ -25,23 +25,24 @@ The options are the following:
25
25
 
26
26
  +:excel+ and +:visible+ are options stated in +:default+ or +:force+
27
27
 
28
- +:excel+:: specifies the Excel instance.
28
+ +:excel+:: specifies the Excel instance.
29
29
 
30
- +:visible+:: makes the workbook visible or invisible
30
+ +:visible+:: makes the workbook visible or invisible
31
31
 
32
- +:if_unsaved+:: specify behaviour if the workbook was unsaved (default: +:raise+)
32
+ +:if_unsaved+:: specifies behaviour if the workbook was unsaved (default: +:raise+)
33
33
 
34
- +:if_obstructed+:: specify behaviour if the workbook is blocked by another book (default: +:raise+)
34
+ +:if_blocked:: specifies behaviour if the workbook is blocked by another book (default: +:raise+)
35
35
 
36
- +:read_only+:: open in read-only mode (default: +false+)
37
36
 
38
- +:check_compatibility:: check compatibility when saving
37
+ +:read_only+:: opens in read-only mode (default: +false+)
39
38
 
40
- +:calculation+:: forces the calculation mode to be manual (:manual) or automatic (:automatic)
39
+ +:check_compatibility:: checks compatibility when saving
41
40
 
42
- +:if_absent+:: specify behaviour if the workbook with the given file name does not exist if the workbook does not exist (default: +create+)
41
+ +:calculation+:: forces the calculation mode to be manual (:manual) or automatic (:automatic)
43
42
 
44
- You can use the following abbreviations: +:f+ for +:force+, +:d+ for +:default+, +:e+ for +:excel+, and +:v+ for +:visible+. Finally you can leave out the option +:force+ or +:f+.
43
+ +:if_absent+:: specifies behaviour if the workbook with the given file name does not exist if the workbook does not exist (default: +create+)
44
+
45
+ You can use the following abbreviations or synonyms: +:f+ for +:force+, +:d+ for +:default+, +:e+ for +:excel+, and +:v+ for +:visible+, +:if_obstructed+ for +:if_blocked+. Finally you can leave out the option +:force+ or +:f+.
45
46
 
46
47
  The option +:excel+ :
47
48
 
@@ -57,7 +58,7 @@ If a workbook contains unsaved changes and a new workbook with the same file nam
57
58
  +:new_excel+:: Open the new workbook in a new Excel instance
58
59
  +:alert+:: Give control to Excel.
59
60
 
60
- The option +:if_obstructed+ :
61
+ The option +:if_blocked+ :
61
62
 
62
63
  If a workbook is open and a new workbook with same name and a different path is open, then
63
64
 
@@ -122,9 +123,9 @@ If a workbook contains unsaved changes and a workbook with the same filename sha
122
123
 
123
124
  book = Workbook.open('spec/data/workbook.xls', :if_unsaved => :accept)
124
125
 
125
- If a workbook is open and a workbook with the same name but in different path shall be opened, i.e. the first workbook blocks opening the other workbook, then the option +:if_obstructed+ handles this situation, e.g.
126
+ If a workbook is open and a workbook with the same name but in different path shall be opened, i.e. the first workbook blocks opening the other workbook, then the option +:if_blocked+ handles this situation, e.g.
126
127
 
127
- book = Workbook.open('path/workbook.xls', :if_obstructed => :forget)
128
+ book = Workbook.open('path/workbook.xls', :if_blocked => :forget)
128
129
 
129
130
  Remarks:
130
131
 
@@ -150,6 +151,10 @@ A Workbook object can be created when giving an Excel workbook.
150
151
 
151
152
  book = Workbook.new(win32ole_workbook)
152
153
 
154
+ Note that only a restricted number of options can be considered, e.g. +:visible+
155
+
156
+ book = Workbook.new(win32ole_workbook, :visible => true)
157
+
153
158
  === Unobtrusively modifying a workbook
154
159
 
155
160
  The method +unobtrusively+ enables the user to read or modify a workbook, no matter if it is open in some Excel instance, if it is saved or unsaved, and if it is writable or not. When opening a workbook unobtrusively, its status remains unchanged. This status includes, whether the workbook is opened or closed, saved or unsaved, readonly or writable, visible or invisible, calculation mode is automatic or manual, and checking compatibility is turned on or off.
@@ -182,7 +187,7 @@ The methods +for_reading+ and +for_modifying+ indicate unobtrusively reading or
182
187
  sheet[1,1] = "c"
183
188
  end
184
189
 
185
- Remark: The methods +unobtrusively+, +for_reading+ and +for_modifying+ work only for workbooks opened via RobustExcelOle. They do not connect to workbooks opened outside RobustExcelOle.
190
+ Note, that the methods +unobtrusively+, +for_reading+ and +for_modifying+ work not only for workbooks opened via RobustExcelOle, but do also connect to workbooks opened outside RobustExcelOle.
186
191
 
187
192
  === Retaining the saved-status
188
193
 
@@ -16,24 +16,25 @@ module RobustExcelOle
16
16
  book.excel
17
17
  end
18
18
 
19
- # returns a book with the given filename, if it was open once
19
+ # returns a workbook with the given filename, if it was open once
20
20
  # @param [String] filename the file name
21
21
  # @param [Hash] options the options
22
22
  # @option option [Boolean] :prefer_writable
23
23
  # @option option [Boolean] :prefer_excel
24
- # prefers open books to closed books, and among them, prefers more recently opened books
24
+ # prefers open workbooks to closed workbooks, and among them, prefers more recently opened workbooks
25
25
  # excludes hidden Excel instance
26
- # options: :prefer_writable returns the writable book, if it is open (default: true)
27
- # otherwise returns the book according to the preference order mentioned above
28
- # :prefer_excel returns the book in the given Excel instance, if it exists,
26
+ # options: :prefer_writable returns the writable workbook, if it is open (default: true)
27
+ # otherwise returns the workbook according to the preference order mentioned above
28
+ # :prefer_excel returns the workbook in the given Excel instance, if it exists,
29
29
  # otherwise proceeds according to prefer_writable
30
30
  def fetch(filename, options = { :prefer_writable => true })
31
31
  return nil unless filename
32
32
 
33
33
  filename = General.absolute_path(filename)
34
34
  filename_key = General.canonize(filename)
35
- weakref_books = @filename2books[filename_key]
36
- return nil unless weakref_books
35
+ weakref_books = @filename2books[filename_key]
36
+ weakref_books = consider_networkpaths(filename_key) if weakref_books.empty? || weakref_books.nil?
37
+ return nil if weakref_books.nil? || weakref_books.empty?
37
38
 
38
39
  result = open_book = closed_book = nil
39
40
  weakref_books = weakref_books.map { |wr_book| wr_book if wr_book.weakref_alive? }.compact
@@ -44,8 +45,7 @@ module RobustExcelOle
44
45
  begin
45
46
  @filename2books[filename_key].delete(wr_book)
46
47
  rescue
47
- # trace "#{$!.message}"
48
- # trace "Warning: deleting dead reference failed: file: #{filename.inspect}"
48
+ trace "Warning: deleting dead reference failed: file: #{filename.inspect}"
49
49
  end
50
50
  else
51
51
  book = wr_book.__getobj__
@@ -67,6 +67,92 @@ module RobustExcelOle
67
67
  result
68
68
  end
69
69
 
70
+ def consider_networkpaths(filename)
71
+ network = WIN32OLE.new('WScript.Network')
72
+ drives = network.enumnetworkdrives
73
+ drive_letter, filename_after_drive_letter = filename.split(':')
74
+ # if filename starts with a drive letter not c and this drive exists,
75
+ # then if there is the corresponding host_share_path in the bookstore,
76
+ # then take the corresponding workbooks
77
+ # otherwise (there is an usual file path) find in the bookstore the workbooks of which filenames
78
+ # ends with the latter part of the given filename (after the drive letter)
79
+ if drive_letter != 'c' && drive_letter != filename
80
+ for i in 0 .. drives.Count-1
81
+ next if i % 2 == 1
82
+ if drives.Item(i).gsub(':','').downcase == drive_letter
83
+ hostname_share = drives.Item(i+1).gsub('\\','/').gsub('//','').downcase
84
+ break
85
+ end
86
+ end
87
+ found_filename = nil
88
+ @filename2books.each do |stored_filename,_|
89
+ if hostname_share && stored_filename
90
+ if stored_filename[0] == '/'
91
+ index_hostname = stored_filename[1,stored_filename.length].index('/')+2
92
+ index_hostname_share = stored_filename[index_hostname,stored_filename.length].index('/')
93
+ hostname_share_in_stored_filename = stored_filename[1,index_hostname+index_hostname_share-1]
94
+ if hostname_share_in_stored_filename == hostname_share
95
+ found_filename = stored_filename
96
+ break
97
+ end
98
+ elsif found_filename.nil? && stored_filename.end_with?(filename_after_drive_letter)
99
+ found_filename = stored_filename
100
+ end
101
+ end
102
+ end
103
+ elsif filename[0] == '/'
104
+ # if filename starts with a host name and share, and this is an existing host name share path,
105
+ # then if there are workbooks with the corresponding drive letter,
106
+ # then take these workbooks,
107
+ # otherwise (there is an usual file path) find in the bookstore the workbooks of which filenames
108
+ # ends with the latter part of the given filename (after the drive letter)
109
+ index_hostname = filename[1,filename.length].index('/')+2
110
+ index_hostname_share = filename[index_hostname,filename.length].index('/')
111
+ hostname_share_in_filename = filename[1,index_hostname+index_hostname_share-1]
112
+ filename_after_hostname_share = filename[index_hostname+index_hostname_share+1, filename.length]
113
+ require 'socket'
114
+ hostname = Socket.gethostname
115
+ if hostname_share_in_filename[0,hostname_share_in_filename.index('/')] == hostname.downcase
116
+ for i in 0 .. drives.Count-1
117
+ next if i % 2 == 1
118
+ hostname_share = drives.Item(i+1).gsub('\\','/').gsub('//','').downcase
119
+ if hostname_share == hostname_share_in_filename
120
+ drive_letter = drives.Item(i).gsub(':','').downcase
121
+ break
122
+ end
123
+ end
124
+ @filename2books.each do |stored_filename,_|
125
+ if stored_filename
126
+ if drive_letter && stored_filename.start_with?(drive_letter.downcase) && stored_filename.end_with?(filename_after_hostname_share)
127
+ found_filename = stored_filename
128
+ break
129
+ elsif found_filename.nil? && stored_filename.end_with?(filename_after_hostname_share)
130
+ found_filename = stored_filename
131
+ end
132
+ end
133
+ end
134
+ end
135
+ else
136
+ # if filename is an usual file path,
137
+ # then find in the bookstore a workbook of which filename starts with
138
+ # a drive letter or a host name
139
+ @filename2books.each do |stored_filename,_|
140
+ drive_letter, _ = stored_filename.split(':')
141
+ stored_filename_end = if drive_letter != stored_filename && drive_letter != 'c'
142
+ stored_filename[stored_filename.index(':')+1,stored_filename.length]
143
+ elsif stored_filename[0] == '/'
144
+ index_after_hostname = stored_filename[1,stored_filename.length].index('/')
145
+ stored_filename[index_after_hostname+1, stored_filename.length]
146
+ end
147
+ if stored_filename_end && filename.end_with?(stored_filename_end)
148
+ found_filename = stored_filename
149
+ break
150
+ end
151
+ end
152
+ end
153
+ @filename2books[found_filename]
154
+ end
155
+
70
156
  # stores a workbook
71
157
  # @param [Workbook] book a given book
72
158
  def store(book)
@@ -76,14 +76,15 @@ module RobustExcelOle
76
76
  ole_xl = win32ole_excel unless win32ole_excel.nil?
77
77
  options = { :reuse => true }.merge(options)
78
78
  #ole_xl = current_excel if options[:reuse] == true
79
- if options[:reuse] == true
80
- ole_xl = if RUBY_PLATFORM =~ /java/
81
- excel_instance = known_excel_instance
82
- ole_xl = excel_instance.ole_excel unless excel_instance.nil?
79
+ if options[:reuse] == true && ole_xl.nil?
80
+ ole_xl = if RUBY_PLATFORM =~ /java/
81
+ excel_instance = known_excel_instance if excel_instance.nil?
82
+ excel_instance.ole_excel unless excel_instance.nil?
83
83
  else
84
84
  current_excel
85
85
  end
86
86
  end
87
+ connected = (not ole_xl.nil?)
87
88
  ole_xl ||= WIN32OLE.new('Excel.Application')
88
89
  hwnd = ole_xl.HWnd
89
90
  stored = hwnd2excel(hwnd)
@@ -98,11 +99,11 @@ module RobustExcelOle
98
99
 
99
100
  unless options.is_a? WIN32OLE
100
101
  begin
101
- reused = options[:reuse] && stored && stored.alive?
102
- unless reused
102
+ reused = options[:reuse] && stored && stored.alive?
103
+ if (not reused) && (not connected)
103
104
  options = { :displayalerts => :if_visible, :visible => false, :screenupdating => true }.merge(options)
104
105
  end
105
- result.visible = options[:visible] unless options[:visible].nil?
106
+ result.visible = options[:visible] unless options[:visible].nil?
106
107
  result.displayalerts = options[:displayalerts] unless options[:displayalerts].nil?
107
108
  result.calculation = options[:calculation] unless options[:calculation].nil?
108
109
  result.screenupdating = options[:screenupdating] unless options[:screenupdating].nil?
@@ -423,6 +424,10 @@ module RobustExcelOle
423
424
  processes.select { |p| p.name == 'EXCEL.EXE' }.size
424
425
  end
425
426
 
427
+ def self.known_excels_number
428
+ @@hwnd2excel.size
429
+ end
430
+
426
431
  # returns an Excel instance
427
432
  def self.known_excel_instance
428
433
  @@hwnd2excel.each do |hwnd, wr_excel|
@@ -67,13 +67,26 @@ class WIN32OLE
67
67
 
68
68
  # promoting WIN32OLE objects to RobustExcelOle objects
69
69
  def to_reo
70
- case ole_type.name
71
- when 'Range' then RobustExcelOle::Range.new(self)
72
- when '_Worksheet' then RobustExcelOle::Worksheet.new(self)
73
- when '_Workbook' then RobustExcelOle::Workbook.new(self)
74
- when '_Application' then RobustExcelOle::Excel.new(self)
75
- else
76
- self
70
+ begin
71
+ self.Hwnd
72
+ RobustExcelOle::Excel.new(self)
73
+ rescue
74
+ begin
75
+ self.FullName
76
+ RobustExcelOle::Workbook.new(self)
77
+ rescue
78
+ begin
79
+ self.Copy
80
+ RobustExcelOle::Worksheet.new(self)
81
+ rescue
82
+ begin
83
+ self.Address
84
+ RobustExcelOle::Range.new(self)
85
+ rescue
86
+ self
87
+ end
88
+ end
89
+ end
77
90
  end
78
91
  end
79
92
  end
@@ -213,6 +213,12 @@ module RobustExcelOle
213
213
  end
214
214
  end
215
215
 
216
+ def == other_range
217
+ other_range.is_a?(Range) &&
218
+ self.worksheet == other_range.worksheet
219
+ self.Address == other_range.Address
220
+ end
221
+
216
222
  # @private
217
223
  def self.worksheet_class
218
224
  @worksheet_class ||= begin
@@ -74,6 +74,14 @@ module RobustExcelOle
74
74
  class WorkbookBeingUsed < WorkbookREOError
75
75
  end
76
76
 
77
+ # @private
78
+ class WorkbookConnectingNotAliveError < WorkbookREOError
79
+ end
80
+
81
+ # @private
82
+ class WorkbookConnectingBlockingError < WorkbookREOError
83
+ end
84
+
77
85
  # @private
78
86
  class FileNotFound < FileREOError
79
87
  end
@@ -1,3 +1,3 @@
1
1
  module RobustExcelOle
2
- VERSION = "1.10"
2
+ VERSION = "1.11"
3
3
  end
@@ -14,7 +14,6 @@ module RobustExcelOle
14
14
  attr_accessor :excel
15
15
  attr_accessor :ole_workbook
16
16
  attr_accessor :stored_filename
17
- attr_accessor :options
18
17
  attr_accessor :modified_cells
19
18
  attr_reader :workbook
20
19
 
@@ -31,137 +30,116 @@ module RobustExcelOle
31
30
  :update_links => :never
32
31
  }.freeze
33
32
 
34
- ABBREVIATIONS = [[:default,:d], [:force, :f], [:excel, :e], [:visible, :v]].freeze
35
-
36
- class << self
37
-
38
- # opens a workbook.
39
- # @param [String] file the file name
40
- # @param [Hash] opts the options
41
- # @option opts [Hash] :default or :d
42
- # @option opts [Hash] :force or :f
43
- # @option opts [Symbol] :if_unsaved :raise (default), :forget, :accept, :alert, :excel, or :new_excel
44
- # @option opts [Symbol] :if_obstructed :raise (default), :forget, :save, :close_if_saved, or _new_excel
45
- # @option opts [Symbol] :if_absent :raise (default) or :create
46
- # @option opts [Boolean] :read_only true (default) or false
47
- # @option opts [Boolean] :update_links :never (default), :always, :alert
48
- # @option opts [Boolean] :calculation :manual, :automatic, or nil (default)
49
- # options:
50
- # :default : if the workbook was already open before, then use (unchange) its properties,
51
- # otherwise, i.e. if the workbook cannot be reopened, use the properties stated in :default
52
- # :force : no matter whether the workbook was already open before, use the properties stated in :force
53
- # :default and :force contain: :excel, :visible
54
- # :excel :current (or :active or :reuse)
55
- # -> connects to a running (the first opened) Excel instance,
56
- # excluding the hidden Excel instance, if it exists,
57
- # otherwise opens in a new Excel instance.
58
- # :new -> opens in a new Excel instance
59
- # <excel-instance> -> opens in the given Excel instance
60
- # :visible true, false, or nil (default)
61
- # alternatives: :default_excel, :force_excel, :visible, :d, :f, :e, :v
62
- # :if_unsaved if an unsaved workbook with the same name is open, then
63
- # :raise -> raises an exception
64
- # :forget -> close the unsaved workbook, open the new workbook
65
- # :accept -> lets the unsaved workbook open
66
- # :alert or :excel -> gives control to Excel
67
- # :new_excel -> opens the new workbook in a new Excel instance
68
- # :if_obstructed if a workbook with the same name in a different path is open, then
69
- # :raise -> raises an exception
70
- # :forget -> closes the old workbook, open the new workbook
71
- # :save -> saves the old workbook, close it, open the new workbook
72
- # :close_if_saved -> closes the old workbook and open the new workbook, if the old workbook is saved,
73
- # otherwise raises an exception.
74
- # :new_excel -> opens the new workbook in a new Excel instance
75
- # :if_absent :raise -> raises an exception , if the file does not exists
76
- # :create -> creates a new Excel file, if it does not exists
77
- # :read_only true -> opens in read-only mode
78
- # :visible true -> makes the workbook visible
79
- # :check_compatibility true -> check compatibility when saving
80
- # :update_links true -> user is being asked how to update links, false -> links are never updated
81
- # @return [Workbook] a representation of a workbook
82
- def open(file, opts = { }, &block)
83
- options = @options = process_options(opts)
84
- book = nil
85
- if (options[:force][:excel] != :new) && (options[:force][:excel] != :reserved_new)
86
- # if readonly is true, then prefer a book that is given in force_excel if this option is set
87
- forced_excel = if options[:force][:excel]
88
- options[:force][:excel] == :current ? excel_class.new(:reuse => true) : excel_of(options[:force][:excel])
89
- end
90
- book = bookstore.fetch(file,
91
- :prefer_writable => !(options[:read_only]),
92
- :prefer_excel => (options[:read_only] ? forced_excel : nil)) rescue nil
93
- if book
94
- if (!(options[:force][:excel]) || (forced_excel == book.excel)) &&
95
- !(book.alive? && !book.saved && (options[:if_unsaved] != :accept))
96
- book.options = options
97
- book.ensure_excel(options) # unless book.excel.alive?
98
- # if the ReadOnly status shall be changed, save, close and reopen it
99
- if book.alive? && ((!book.writable && !(options[:read_only])) ||
100
- (book.writable && options[:read_only]))
101
- book.save if book.writable && !book.saved
102
- book.close(:if_unsaved => :forget)
103
- end
104
- # reopens the book if it was closed
105
- book.ensure_workbook(file,options) unless book.alive?
106
- book.visible = options[:force][:visible] unless options[:force][:visible].nil?
107
- book.CheckCompatibility = options[:check_compatibility] unless options[:check_compatibility].nil?
108
- book.excel.calculation = options[:calculation] unless options[:calculation].nil?
109
- return book
110
- end
33
+ ABBREVIATIONS = [[:default,:d], [:force, :f], [:excel, :e], [:visible, :v],
34
+ [:if_obstructed, :if_blocked]].freeze
35
+
36
+
37
+ # opens a workbook.
38
+ # @param [String] file the file name
39
+ # @param [Hash] opts the options
40
+ # @option opts [Hash] :default or :d
41
+ # @option opts [Hash] :force or :f
42
+ # @option opts [Symbol] :if_unsaved :raise (default), :forget, :accept, :alert, :excel, or :new_excel
43
+ # @option opts [Symbol] :if_obstructed :raise (default), :forget, :save, :close_if_saved, or _new_excel
44
+ # @option opts [Symbol] :if_absent :raise (default) or :create
45
+ # @option opts [Boolean] :read_only true (default) or false
46
+ # @option opts [Boolean] :update_links :never (default), :always, :alert
47
+ # @option opts [Boolean] :calculation :manual, :automatic, or nil (default)
48
+ # options:
49
+ # :default : if the workbook was already open before, then use (unchange) its properties,
50
+ # otherwise, i.e. if the workbook cannot be reopened, use the properties stated in :default
51
+ # :force : no matter whether the workbook was already open before, use the properties stated in :force
52
+ # :default and :force contain: :excel, :visible
53
+ # :excel :current (or :active or :reuse)
54
+ # -> connects to a running (the first opened) Excel instance,
55
+ # excluding the hidden Excel instance, if it exists,
56
+ # otherwise opens in a new Excel instance.
57
+ # :new -> opens in a new Excel instance
58
+ # <excel-instance> -> opens in the given Excel instance
59
+ # :visible true, false, or nil (default)
60
+ # alternatives: :default_excel, :force_excel, :visible, :d, :f, :e, :v
61
+ # :if_unsaved if an unsaved workbook with the same name is open, then
62
+ # :raise -> raises an exception
63
+ # :forget -> close the unsaved workbook, open the new workbook
64
+ # :accept -> lets the unsaved workbook open
65
+ # :alert or :excel -> gives control to Excel
66
+ # :new_excel -> opens the new workbook in a new Excel instance
67
+ # :if_obstructed if a workbook with the same name in a different path is open, then
68
+ # or :raise -> raises an exception
69
+ # :if_blocked :forget -> closes the old workbook, open the new workbook
70
+ # :save -> saves the old workbook, close it, open the new workbook
71
+ # :close_if_saved -> closes the old workbook and open the new workbook, if the old workbook is saved,
72
+ # otherwise raises an exception.
73
+ # :new_excel -> opens the new workbook in a new Excel instance
74
+ # :if_absent :raise -> raises an exception , if the file does not exists
75
+ # :create -> creates a new Excel file, if it does not exists
76
+ # :read_only true -> opens in read-only mode
77
+ # :visible true -> makes the workbook visible
78
+ # :check_compatibility true -> check compatibility when saving
79
+ # :update_links true -> user is being asked how to update links, false -> links are never updated
80
+ # @return [Workbook] a representation of a workbook
81
+ def self.open(file, opts = { }, &block)
82
+ raise(FileNameNotGiven, 'filename is nil') if file.nil?
83
+ raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
84
+ options = process_options(opts)
85
+ book = nil
86
+ if options[:force][:excel] != :new
87
+ # if readonly is true, then prefer a book that is given in force_excel if this option is set
88
+ forced_excel = if RUBY_PLATFORM !~ /java/
89
+ (options[:force][:excel].nil? || options[:force][:excel] == :current) ?
90
+ excel_class.new(:reuse => true) : excel_of(options[:force][:excel])
91
+ end
92
+ begin
93
+ book = if File.exists?(file)
94
+ bookstore.fetch(file, :prefer_writable => !(options[:read_only]),
95
+ :prefer_excel => (options[:read_only] ? forced_excel : nil))
111
96
  end
97
+ rescue
98
+ trace "#{$!.message}"
112
99
  end
113
- new(file, options, &block)
114
- end
115
- end
116
-
117
- # creates a Workbook object by opening an Excel file given its filename
118
- # or by promoting a Win32OLE object representing an Excel file
119
- # @param [WIN32OLE] workbook a Win32Ole-workbook
120
- # @param [Hash] opts the options as in Workbook::open
121
- # @option opts [Symbol] see Workbook::open
122
- # @return [Workbook] a workbook
123
- def self.new(ole_workbook, opts = { }, &block)
124
- opts = process_options(opts)
125
- if ole_workbook && (ole_workbook.is_a? WIN32OLE)
126
- filename = ole_workbook.Fullname.tr('\\','/') rescue nil
127
- if filename
128
- book = bookstore.fetch(filename)
129
- if book && book.alive?
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?
100
+ if book
101
+ if (!(options[:force][:excel]) || (forced_excel == book.excel)) &&
102
+ !(book.alive? && !book.saved && (options[:if_unsaved] != :accept))
103
+ book.ensure_excel(options) # unless book.excel.alive?
104
+ # if the ReadOnly status shall be changed, save, close and reopen it
105
+ # removed the feature for the next time
106
+ if book.alive? && ((!book.writable && !(options[:read_only])) ||
107
+ (book.writable && options[:read_only]))
108
+ book.save if book.writable && !book.saved
109
+ book.close(:if_unsaved => :forget)
110
+ end
111
+ # reopens the book if it was closed
112
+ book.ensure_workbook(file,options) unless book.alive?
113
+ book.visible = options[:force][:visible] unless options[:force][:visible].nil?
114
+ book.CheckCompatibility = options[:check_compatibility] unless options[:check_compatibility].nil?
115
+ book.excel.calculation = options[:calculation] unless options[:calculation].nil?
134
116
  return book
135
- else
136
- super(filename,opts)
137
117
  end
138
118
  end
139
- else
140
- super
141
119
  end
120
+ new(file, options, &block)
142
121
  end
143
-
122
+
144
123
  # creates a new Workbook object, if a file name is given
145
124
  # Promotes the win32ole workbook to a Workbook object, if a win32ole-workbook is given
146
125
  # @param [Variant] file_or_workbook file name or workbook
147
- # @param [Hash] opts the options as in Workbook::open
126
+ # @param [Hash] opts
148
127
  # @option opts [Symbol] see above
149
128
  # @return [Workbook] a workbook
150
129
  def initialize(file_or_workbook, options = { }, &block)
151
- # options = @options = self.class.process_options(options) if options.empty?
152
- if file_or_workbook.is_a? WIN32OLE
130
+ options = self.class.process_options(options)
131
+ if file_or_workbook.is_a? WIN32OLE
153
132
  workbook = file_or_workbook
154
133
  @ole_workbook = workbook
155
134
  # use the Excel instance where the workbook is opened
156
- win32ole_excel = WIN32OLE.connect(workbook.Fullname).Application rescue nil
157
- @excel = excel_class.new(win32ole_excel)
158
- @excel.visible = options[force][:visible] unless options[:force][:visible].nil?
135
+ ole_excel = WIN32OLE.connect(workbook.Fullname).Application rescue nil
136
+ @excel = excel_class.new(ole_excel)
137
+ @excel.visible = options[:force][:visible] unless options[:force][:visible].nil?
159
138
  @excel.calculation = options[:calculation] unless options[:calculation].nil?
160
- ensure_excel(options)
161
139
  else
162
- file = file_or_workbook
140
+ filename = file_or_workbook
163
141
  ensure_excel(options)
164
- ensure_workbook(file, options)
142
+ ensure_workbook(filename, options)
165
143
  end
166
144
  bookstore.store(self)
167
145
  @modified_cells = []
@@ -177,7 +155,7 @@ module RobustExcelOle
177
155
  end
178
156
  end
179
157
 
180
- private
158
+ private
181
159
 
182
160
  # translates abbreviations and synonyms and merges with default options
183
161
  def self.process_options(options, proc_opts = {:use_defaults => true})
@@ -241,45 +219,81 @@ module RobustExcelOle
241
219
  public
242
220
 
243
221
  # @private
244
- def ensure_excel(options)
222
+ # ensures an excel but not for jruby if current Excel shall be used
223
+ def ensure_excel(options)
224
+ excel_option = options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
225
+ @excel = if excel_option == :new
226
+ excel_class.new(:reuse => false)
227
+ elsif excel_option.nil? || excel_option == :current
228
+ excel_class.new(:reuse => true) unless RUBY_PLATFORM =~ /java/
229
+ else #elsif excel_options.is_a?(WIN32OLE) || excel_option.is_a?(Excel) #excel_option.respond_to?(:Hwnd)
230
+ self.class.excel_of(excel_option)
231
+ end
245
232
  if @excel && @excel.alive?
246
- @excel.created = false
247
- return
233
+ @excel.visible = options[:force][:visible] unless options[:force][:visible].nil?
234
+ @excel.calculation = options[:calculation] unless options[:calculation].nil?
248
235
  end
249
- excel_option = options[:force].nil? || options[:force][:excel].nil? ? options[:default][:excel] : options[:force][:excel]
250
- @excel = self.class.excel_of(excel_option) unless excel_option == :current || excel_option == :new || excel_option == :reserved_new
251
- excel_class.new(:reuse => false) if (excel_option == :reserved_new) && Excel.known_excel_instances.empty?
252
- @excel = excel_class.new(:reuse => (excel_option == :current)) unless @excel && @excel.alive?
253
- @excel
254
236
  end
255
237
 
256
238
 
257
239
  # @private
240
+ # restriction for jruby: does not manage conflicts with blocking or unsaved workbooks
258
241
  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
242
+ return if @ole_workook && alive?
243
+ filename = @stored_filename ? @stored_filename : filename
264
244
  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)
245
+ if RUBY_PLATFORM =~ /java/ && (options[:force][:excel].nil? || options[:force][:excel] == :current)
246
+ begin
247
+ connect(filename,options)
248
+ rescue WorkbookConnectingNotAliveError
249
+ raise WorkbookNotSaved, "workbook is already open but not saved: #{File.basename(filename).inspect}"
250
+ rescue WorkbookConnectingBlockingError # can't find moniker
251
+ raise WorkbookBlocked, "can't open workbook #{filename}"+
252
+ "\nbecause it is being blocked by a workbook with the same name in a different path."
253
+ end
269
254
  else
270
- open_or_create_workbook(filename, options)
255
+ workbooks = @excel.Workbooks
256
+ @ole_workbook = workbooks.Item(File.basename(filename)) rescue nil if @ole_workbook.nil?
257
+ if @ole_workbook
258
+ manage_blocking_or_unsaved_workbook(filename,options)
259
+ else
260
+ if options[:force][:excel].nil? || options[:force][:excel] == :current
261
+ connect(filename,options)
262
+ else
263
+ open_or_create_workbook(filename,options)
264
+ end
265
+ end
271
266
  end
267
+ set_options(options)
272
268
  end
273
269
 
274
270
  private
275
271
 
276
272
  # @private
277
- def manage_nonexisting_file(filename,options)
273
+ # connects to an unknown workbook and returns true, if it exists
274
+ def connect(filename,options)
275
+ abs_filename = General.absolute_path(filename).tr('/','\\')
276
+ @ole_workbook = begin
277
+ WIN32OLE.connect(abs_filename)
278
+ rescue
279
+ raise WorkbookConnectingBlockingError
280
+ end
281
+ ole_excel = begin
282
+ WIN32OLE.connect(@ole_workbook.Fullname).Application
283
+ rescue
284
+ raise WorkbookConnectingNotAliveError
285
+ end
286
+ @excel = excel_class.new(ole_excel)
287
+ end
288
+
289
+ # @private
290
+ def manage_nonexisting_file(filename,options)
278
291
  return if File.exist?(filename)
292
+ abs_filename = General.absolute_path(filename).tr('/','\\')
279
293
  if options[:if_absent] == :create
280
- excel.Workbooks.Add
294
+ ensure_excel({:force[:excel] => :new}.merge(options)) if RUBY_PLATFORM !~ /java/
295
+ @excel.Workbooks.Add
281
296
  empty_ole_workbook = excel.Workbooks.Item(excel.Workbooks.Count)
282
- abs_filename = General.absolute_path(filename).tr('/','\\')
283
297
  begin
284
298
  empty_ole_workbook.SaveAs(abs_filename)
285
299
  rescue # WIN32OLERuntimeError => msg
@@ -310,7 +324,7 @@ module RobustExcelOle
310
324
  manage_blocking_workbook(filename,options)
311
325
  else
312
326
  unless @ole_workbook.Saved
313
- # workbook open and writable, not obstructed by another workbook, but not saved
327
+ # workbook open and writable, not obstructed by another workbook, but not saved
314
328
  manage_unsaved_workbook(filename,options)
315
329
  end
316
330
  end
@@ -320,12 +334,13 @@ module RobustExcelOle
320
334
  def manage_blocking_workbook(filename,options)
321
335
  case options[:if_obstructed]
322
336
  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"
337
+ raise WorkbookBlocked, "can't open workbook #{filename},
338
+ because it is being blocked by #{@ole_workbook.Fullname.tr('\\','/')} with the same name in a different path." +
339
+ "\nHint: Use the option :if_blocked with values :forget or :save,
340
+ to allow automatic closing of the old workbook (without or with saving before, respectively),
341
+ before the new workbook is being opened."
327
342
  when :forget
328
- @ole_workbook.Close
343
+ @excel.with_displayalerts(false) { @ole_workbook.Close }
329
344
  @ole_workbook = nil
330
345
  open_or_create_workbook(filename, options)
331
346
  when :save
@@ -345,11 +360,12 @@ module RobustExcelOle
345
360
  @excel = excel_class.new(:reuse => false)
346
361
  open_or_create_workbook(filename, options)
347
362
  else
348
- raise OptionInvalid, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}" +
363
+ raise OptionInvalid, ":if_blocked: invalid option: #{options[:if_obstructed].inspect}" +
349
364
  "\nHint: Valid values are :raise, :forget, :save, :close_if_saved, :new_excel"
350
365
  end
351
366
  end
352
367
 
368
+ # @private
353
369
  def manage_unsaved_workbook(filename,options)
354
370
  case options[:if_unsaved]
355
371
  when :raise
@@ -357,7 +373,7 @@ module RobustExcelOle
357
373
  "\nHint: Save the workbook or open the workbook using option :if_unsaved with values :forget and :accept to
358
374
  close the unsaved workbook and reopen it, or to let the unsaved workbook open, respectively"
359
375
  when :forget
360
- @ole_workbook.Close
376
+ @excel.with_displayalerts(false) { @ole_workbook.Close }
361
377
  @ole_workbook = nil
362
378
  open_or_create_workbook(filename, options)
363
379
  when :accept
@@ -373,8 +389,6 @@ module RobustExcelOle
373
389
  end
374
390
  end
375
391
 
376
- private
377
-
378
392
  # @private
379
393
  def open_or_create_workbook(filename, options)
380
394
  if !@ole_workbook || (options[:if_unsaved] == :alert) || options[:if_obstructed]
@@ -404,18 +418,6 @@ module RobustExcelOle
404
418
  rescue WIN32OLERuntimeError => msg
405
419
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message}"
406
420
  end
407
- if options[:force][:visible].nil? && !options[:default][:visible].nil?
408
- if @excel.created
409
- self.visible = options[:default][:visible]
410
- else
411
- self.window_visible = options[:default][:visible]
412
- end
413
- else
414
- self.visible = options[:force][:visible] unless options[:force][:visible].nil?
415
- end
416
- @ole_workbook.CheckCompatibility = options[:check_compatibility]
417
- @excel.calculation = options[:calculation] unless options[:calculation].nil?
418
- self.Saved = true # unless self.Saved # ToDo: this is too hard
419
421
  rescue WIN32OLERuntimeError => msg
420
422
  raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message} #{msg.backtrace}"
421
423
  end
@@ -423,6 +425,23 @@ module RobustExcelOle
423
425
  end
424
426
  end
425
427
 
428
+ # @private
429
+ def set_options(options)
430
+ if options[:force][:visible].nil? && !options[:default][:visible].nil?
431
+ if @excel.created
432
+ self.visible = options[:default][:visible]
433
+ else
434
+ self.window_visible = options[:default][:visible]
435
+ end
436
+ else
437
+ self.visible = options[:force][:visible] unless options[:force][:visible].nil?
438
+ end
439
+ @excel.calculation = options[:calculation] unless options[:calculation].nil?
440
+ @ole_workbook.CheckCompatibility = options[:check_compatibility]
441
+ @ole_workbook.Saved = true # unless self.Saved # ToDo: this is too hard
442
+ end
443
+
444
+ # @private
426
445
  # translating the option UpdateLinks from REO to VBA
427
446
  # setting UpdateLinks works only if calculation mode is automatic,
428
447
  # parameter 'UpdateLinks' has no effect
@@ -435,6 +454,7 @@ module RobustExcelOle
435
454
  end
436
455
  end
437
456
 
457
+ # @private
438
458
  # workaround for linked workbooks for Excel 2007:
439
459
  # opening and closing a dummy workbook if Excel has no workbooks.
440
460
  # delay: with visible: 0.2 sec, without visible almost none
@@ -504,6 +524,7 @@ module RobustExcelOle
504
524
 
505
525
  private
506
526
 
527
+ # @private
507
528
  def close_workbook
508
529
  @ole_workbook.Close if alive?
509
530
  @ole_workbook = nil unless alive?
@@ -537,9 +558,9 @@ module RobustExcelOle
537
558
  unobtrusively(*args, &block)
538
559
  end
539
560
 
561
+
540
562
  # allows to read or modify a workbook such that its state remains unchanged
541
563
  # state comprises: open, saved, writable, visible, calculation mode, check compatibility
542
- # remarks: works only for workbooks opened with RobustExcelOle
543
564
  # @param [String] file the file name
544
565
  # @param [Hash] opts the options
545
566
  # @option opts [Variant] :if_closed :current (default), :new or an Excel instance
@@ -557,7 +578,7 @@ module RobustExcelOle
557
578
  prefer_writable = ((!(opts[:read_only]) || opts[:writable] == true) &&
558
579
  !(opts[:read_only].nil? && opts[:writable] == false))
559
580
  do_not_write = (opts[:read_only] || (opts[:read_only].nil? && opts[:writable] == false))
560
- book = bookstore.fetch(file, :prefer_writable => prefer_writable)
581
+ book = bookstore.fetch(file, :prefer_writable => prefer_writable)
561
582
  was_open = book && book.alive?
562
583
  if was_open
563
584
  was_saved = book.saved
@@ -746,7 +767,11 @@ module RobustExcelOle
746
767
  # closes a given file if it is open
747
768
  # @options opts [Symbol] :if_unsaved
748
769
  def self.close(file, opts = {:if_unsaved => :raise})
749
- book = bookstore.fetch(file) rescue nil
770
+ book = begin
771
+ bookstore.fetch(file)
772
+ rescue
773
+ nil
774
+ end
750
775
  book.close(opts) if book && book.alive?
751
776
  end
752
777
 
@@ -758,7 +783,11 @@ module RobustExcelOle
758
783
 
759
784
  # saves a given file under a new name if it is open
760
785
  def self.save_as(file, new_file, opts = { })
761
- book = bookstore.fetch(file) rescue nil
786
+ book = begin
787
+ bookstore.fetch(file)
788
+ rescue
789
+ nil
790
+ end
762
791
  book.save_as(new_file, opts) if book && book.alive?
763
792
  end
764
793
 
@@ -807,11 +836,12 @@ module RobustExcelOle
807
836
  sheet.Copy({ after_or_before.to_s => base_sheet.ole_worksheet })
808
837
  else
809
838
  @ole_workbook.Worksheets.Add({ after_or_before.to_s => base_sheet.ole_worksheet })
839
+ #@ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count).Activate
810
840
  end
811
841
  else
812
842
  # workaround for jruby
813
843
  if after_or_before == :before
814
- if sheet
844
+ if sheet
815
845
  sheet.Copy(base_sheet.ole_worksheet)
816
846
  else
817
847
  ole_workbook.Worksheets.Add(base_sheet.ole_worksheet)
@@ -835,10 +865,14 @@ module RobustExcelOle
835
865
  end
836
866
  end
837
867
  end
838
- rescue WIN32OLERuntimeError
839
- raise WorksheetREOError, "given sheet #{sheet.inspect} not found"
868
+ rescue #WIN32OLERuntimeError
869
+ trace "#{$!.message}"
870
+ raise WorksheetREOError, "could not add given worksheet #{sheet.inspect}"
840
871
  end
841
- new_sheet = worksheet_class.new(@excel.Activesheet)
872
+ #ole_sheet = @excel.Activesheet
873
+ ole_sheet = ole_workbook.Activesheet
874
+ #ole_sheet = ole_sheet.nil? ? ole_workbook.Worksheets.Item(ole_workbook.Worksheets.Count) : ole_sheet
875
+ new_sheet = worksheet_class.new(ole_sheet)
842
876
  new_sheet.name = new_sheet_name if new_sheet_name
843
877
  new_sheet
844
878
  end
@@ -942,8 +976,10 @@ module RobustExcelOle
942
976
  end
943
977
 
944
978
  # makes both the Excel instance and the window of the workbook visible, or the window invisible
979
+ # does not do anything if geben visible_value is nil
945
980
  # @param [Boolean] visible_value determines whether the workbook shall be visible
946
981
  def visible= visible_value
982
+ return if visible_value.nil?
947
983
  @excel.visible = true if visible_value
948
984
  self.window_visible = visible_value
949
985
  end