robust_excel_ole 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -21,6 +21,7 @@ begin
21
21
  #base_name = workbook_name.sub(/^.*(\.[^.])$/; '')
22
22
  #base_name = workbook_name[0,workbook_name.rindex('.')]
23
23
  #suffix = workbook_name[workbook_name.rindex('.')+1,workbook_name.length]
24
+ #suffix = workbook_name.scan(/\.[^.\/]+$/).last
24
25
  file_name = dir + "/" + workbook_name
25
26
  extended_file_name = dir + "/" + base_name + "_named" + "." + suffix
26
27
  FileUtils.copy file_name, extended_file_name
@@ -0,0 +1,52 @@
1
+
2
+
3
+ #require 'lib/robust_excel_ole'
4
+ include REO
5
+
6
+ require 'irb/completion'
7
+ require 'irb/ext/save-history'
8
+
9
+ ARGV.concat [ "--readline",
10
+ "--prompt-mode",
11
+ "simple" ]
12
+
13
+ # 250 entries in the list
14
+ IRB.conf[:SAVE_HISTORY] = 250
15
+
16
+ # Store results in home directory with specified file name
17
+ #IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-history"
18
+ IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.reo-history"
19
+
20
+ module Readline # :nodoc: #
21
+ module Hist # :nodoc: #
22
+ LOG = IRB.conf[:HISTORY_FILE]
23
+ # LOG = "#{ENV['HOME']}/.irb-history"
24
+
25
+ def self.write_log(line)
26
+ File.open(LOG, 'ab') {|f| f << "#{line}
27
+ "}
28
+ end
29
+
30
+ def self.start_session_log
31
+ timestamp = proc{ Time.now.strftime("%Y-%m-%d, %H:%M:%S")}
32
+ class <<timestamp # :nodoc: #
33
+ alias to_s call
34
+ end
35
+ write_log( "###### session start: #{timestamp}")
36
+ at_exit { write_log("###### session stop: #{timestamp}") }
37
+ end
38
+ end
39
+
40
+ alias :old_readline :readline
41
+ def readline(*args)
42
+ ln = old_readline(*args)
43
+ begin
44
+ Hist.write_log(ln)
45
+ rescue
46
+ end
47
+ ln
48
+ end
49
+ end
50
+
51
+ Readline::Hist.start_session_log
52
+ puts "REO console started"
@@ -11,8 +11,34 @@ require File.join(File.dirname(__FILE__), 'robust_excel_ole/version')
11
11
 
12
12
  REO = RobustExcelOle
13
13
 
14
+ include Enumerable
15
+
16
+ LOG_TO_STDOUT = true
17
+
18
+ REO_LOG_FILE = "reo.log"
19
+ REO_LOG_DIR = ""
20
+
21
+ File.delete REO_LOG_FILE rescue nil
22
+
14
23
  module RobustExcelOle
15
24
 
25
+ def t(text)
26
+ if LOG_TO_STDOUT
27
+ puts text
28
+ else
29
+ if REO_LOG_DIR.empty?
30
+ homes = ["HOME", "HOMEPATH"]
31
+ home = homes.detect {|h| ENV[h] != nil}
32
+ reo_log_dir = ENV[home]
33
+ else
34
+ reo_log_dir = REO_LOG_DIR
35
+ end
36
+ File.open(reo_log_dir + "/" + REO_LOG_FILE,"a") do | file |
37
+ file.puts text
38
+ end
39
+ end
40
+ end
41
+
16
42
  def absolute_path(file)
17
43
  file = File.expand_path(file)
18
44
  file = RobustExcelOle::Cygwin.cygpath('-w', file) if RUBY_PLATFORM =~ /cygwin/
@@ -20,25 +46,33 @@ module RobustExcelOle
20
46
  end
21
47
 
22
48
  def canonize(filename)
23
- raise "No string given to canonize, but #{filename.inspect}" unless filename.is_a?(String)
24
- filename.downcase rescue nil
49
+ raise ExcelError, "No string given to canonize, but #{filename.inspect}" unless filename.is_a?(String)
50
+ normalize(filename).downcase rescue nil
51
+ end
52
+
53
+ def normalize(path)
54
+ path = path.gsub('/./', '/') + '/'
55
+ path = path.gsub(/[\/\\]+/, "/")
56
+ nil while path.gsub!(/(\/|^)(?!\.\.?)([^\/]+)\/\.\.\//, '\1')
57
+ path = path.chomp("/")
58
+ path
25
59
  end
26
60
 
27
- module_function :absolute_path, :canonize
61
+ module_function :t, :absolute_path, :canonize
28
62
 
29
63
  class VBAMethodMissingError < RuntimeError # :nodoc: #
30
64
  end
31
65
 
32
66
  end
33
67
 
34
- class Object
68
+ class Object # :nodoc: #
35
69
  def excel
36
- raise ExcelErrorOpen, "provided instance is neither an Excel nor a Book"
70
+ raise ExcelError, "receiver instance is neither an Excel nor a Book"
37
71
  end
38
72
 
39
73
  end
40
74
 
41
- class ::String
75
+ class ::String # :nodoc: #
42
76
  def / path_part
43
77
  if empty?
44
78
  path_part
@@ -96,7 +130,7 @@ class ::String
96
130
  end
97
131
 
98
132
  # taken from http://api.rubyonrails.org/v2.3.8/classes/ActiveSupport/CoreExtensions/Module.html#M000806
99
- class Module
133
+ class Module # :nodoc: #
100
134
  def parent_name
101
135
  unless defined? @parent_name
102
136
  @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
@@ -14,7 +14,7 @@ module RobustExcelOle
14
14
  DEFAULT_OPEN_OPTS = {
15
15
  :excel => :reuse,
16
16
  :default_excel => :reuse,
17
- :if_lockraiseed => :readonly,
17
+ :if_lockraiseed => :readonly,
18
18
  :if_unsaved => :raise,
19
19
  :if_obstructed => :raise,
20
20
  :if_absent => :raise,
@@ -23,73 +23,101 @@ module RobustExcelOle
23
23
 
24
24
  class << self
25
25
 
26
- # opens a book.
26
+ # opens a workbook.
27
27
  #
28
- # when reopening a book that was opened and closed before, transparency identity is ensured:
28
+ # when reopening a workbook that was opened and closed before, transparency identity is ensured:
29
29
  # same Book objects refer to the same Excel files, and vice versa
30
30
  #
31
31
  # options:
32
- # :default_excel if the book was already open in an Excel instance, then open it there.
33
- # Otherwise, i.e. if the book was not open before or the Excel instance is not alive
34
- # :reuse (default) -> connect to a (the first opened) running Excel instance,
32
+ # :default_excel if the workbook was already open in an Excel instance, then open it there.
33
+ # Otherwise, i.e. if the workbook was not open before or the Excel instance is not alive
34
+ # :reuse (default) -> connects to a (the first opened) running Excel instance,
35
35
  # excluding the hidden Excel instance, if it exists,
36
- # otherwise open in a new Excel instance.
37
- # :new -> open in a new Excel instance
38
- # <excel-instance> -> open in the given Excel instance
39
- # :force_excel no matter whether the book was already open
40
- # :new (default) -> open in a new Excel
41
- # <excel-instance> -> open in the given Excel instance
42
- # :if_unsaved if an unsaved book with the same name is open, then
43
- # :raise (default) -> raise an exception
44
- # :forget -> close the unsaved book, open the new book
45
- # :accept -> let the unsaved book open
46
- # :alert -> give control to Excel
47
- # :new_excel -> open the new book in a new Excel
48
- # :if_obstructed if a book with the same name in a different path is open, then
49
- # :raise (default) -> raise an exception
50
- # :forget -> close the old book, open the new book
51
- # :save -> save the old book, close it, open the new book
52
- # :close_if_saved -> close the old book and open the new book, if the old book is saved,
53
- # otherwise raise an exception.
54
- # :new_excel -> open the new book in a new Excel
36
+ # otherwise opens in a new Excel instance.
37
+ # :new -> opens in a new Excel instance
38
+ # <excel-instance> -> opens in the given Excel instance
39
+ # :force_excel no matter whether the workbook was already open
40
+ # :new (default) -> opens in a new Excel instance
41
+ # <excel-instance> -> opens in the given Excel instance
42
+ # :if_unsaved if an unsaved workbook with the same name is open, then
43
+ # :raise (default) -> raises an exception
44
+ # :forget -> close the unsaved workbook, open the new workbook
45
+ # :accept -> lets the unsaved workbook open
46
+ # :alert -> gives control to Excel
47
+ # :new_excel -> opens the new workbook in a new Excel instance
48
+ # :if_obstructed if a workbook with the same name in a different path is open, then
49
+ # :raise (default) -> raises an exception
50
+ # :forget -> closes the old workbook, open the new workbook
51
+ # :save -> saves the old workbook, close it, open the new workbook
52
+ # :close_if_saved -> closes the old workbook and open the new workbook, if the old workbook is saved,
53
+ # otherwise raises an exception.
54
+ # :new_excel -> opens the new workbook in a new Excel instance
55
55
  # :if_absent :raise (default) -> raises an exception , if the file does not exists
56
56
  # :create -> creates a new Excel file, if it does not exists
57
57
  #
58
- # :read_only open in read-only mode (default: false)
59
- # :displayalerts enable DisplayAlerts in Excel (default: false)
60
- # :visible make visible in Excel (default: false)
58
+ # :read_only opens in read-only mode (default: false)
59
+ # :displayalerts enables DisplayAlerts in Excel (default: false)
60
+ # :visible makes visible in Excel (default: false)
61
61
  # if :default_excel is set, then DisplayAlerts and Visible are set only if these parameters are given
62
62
 
63
63
  def open(file, opts={ }, &block)
64
- current_options = DEFAULT_OPEN_OPTS.merge(opts)
64
+ options = DEFAULT_OPEN_OPTS.merge(opts)
65
65
  book = nil
66
- if (not (current_options[:force_excel] == :new && (not current_options[:if_locked] == :take_writable)))
66
+ if (not (options[:force_excel] == :new && (not options[:if_locked] == :take_writable)))
67
67
  # if readonly is true, then prefer a book that is given in force_excel if this option is set
68
68
  book = bookstore.fetch(file,
69
- :prefer_writable => (not current_options[:read_only]),
70
- :prefer_excel => (current_options[:read_only] ? current_options[:force_excel].excel : nil)) rescue nil
69
+ :prefer_writable => (not options[:read_only]),
70
+ :prefer_excel => (options[:read_only] ? options[:force_excel].excel : nil)) rescue nil
71
71
  if book
72
- if (((not current_options[:force_excel]) || (current_options[:force_excel].excel == book.excel)) &&
73
- (not (book.alive? && (not book.saved) && (not current_options[:if_unsaved] == :accept))))
72
+ if (((not options[:force_excel]) || (options[:force_excel].excel == book.excel)) &&
73
+ (not (book.alive? && (not book.saved) && (not options[:if_unsaved] == :accept))))
74
74
  book.options = DEFAULT_OPEN_OPTS.merge(opts)
75
- book.get_excel unless book.excel.alive?
75
+ book.ensure_excel(options) unless book.excel.alive?
76
76
  # if the book is opened as readonly and should be opened as writable, then close it and open the book with the new readonly mode
77
- book.close if (book.alive? && (not book.writable) && (not current_options[:read_only]))
78
- # reopen the book
79
- book.get_workbook(file) unless book.alive?
77
+ book.close if (book.alive? && (not book.writable) && (not options[:read_only]))
78
+ # reopens the book
79
+ book.ensure_workbook(file,options) unless book.alive?
80
80
  return book
81
81
  end
82
82
  end
83
83
  end
84
- current_options[:excel] = current_options[:force_excel] ? current_options[:force_excel] : current_options[:default_excel]
85
- new(file, current_options, &block)
84
+ #options[:excel] = options[:force_excel] ? options[:force_excel] : options[:default_excel]
85
+ new(file, options, &block)
86
+ end
87
+ end
88
+
89
+ # creates a Book object for a given workbook or file name
90
+ def self.new(workbook, opts={ }, &block)
91
+ if workbook && workbook.class == WIN32OLE
92
+ filename = workbook.Fullname.tr('\\','/') rescue nil
93
+ if filename
94
+ book = bookstore.fetch(filename)
95
+ return book if book && book.alive?
96
+ end
97
+ end
98
+ super
99
+ end
100
+
101
+ # creates a new Book object, if a file name is given
102
+ # lifts the workbook to a Book object, if a workbook is given
103
+ def initialize(file_or_workbook, opts={ }, &block)
104
+ options = DEFAULT_OPEN_OPTS.merge(opts)
105
+ options[:excel] = options[:force_excel] ? options[:force_excel] : options[:default_excel]
106
+ if file_or_workbook.class == WIN32OLE
107
+ workbook = file_or_workbook
108
+ @workbook = workbook
109
+ # use the Excel instance where the workbook is opened
110
+ win32ole_excel = WIN32OLE.connect(workbook.Fullname).Application rescue nil
111
+ options = {:reuse => win32ole_excel}.merge(options)
112
+ @excel = Excel.new(options)
113
+ # if the Excel could not be lifted up, then create it
114
+ ensure_excel(options) unless (@excel && @excel.alive?)
115
+ t "@excel: #{@excel}"
116
+ else
117
+ file = file_or_workbook
118
+ ensure_excel(options)
119
+ ensure_workbook(file, options)
86
120
  end
87
- end
88
-
89
- def initialize(file, opts={ }, &block)
90
- @options = DEFAULT_OPEN_OPTS.merge(opts)
91
- get_excel
92
- get_workbook file
93
121
  bookstore.store(self)
94
122
  if block
95
123
  begin
@@ -100,49 +128,42 @@ module RobustExcelOle
100
128
  end
101
129
  end
102
130
 
103
- def self.excel_class
104
- @excel_class ||= begin
105
- module_name = self.parent_name
106
- "#{module_name}::Excel".constantize
107
- rescue NameError => e
108
- Excel
109
- end
110
- end
111
-
112
- def excel_class
113
- self.class.excel_class
114
- end
115
-
116
-
117
- def get_excel
118
- if @options[:excel] == :reuse
131
+ def ensure_excel(options)
132
+ if options[:excel] == :reuse
119
133
  @excel = excel_class.new(:reuse => true)
120
134
  end
121
- @excel_options = nil
122
- if (not @excel)
123
- if @options[:excel] == :new
124
- @excel_options = {:displayalerts => false, :visible => false}.merge(@options)
125
- @excel_options[:reuse] = false
126
- @excel = excel_class.new(@excel_options)
127
- else
128
- @excel = @options[:excel].excel
135
+ excel_options = nil
136
+ if @excel
137
+ dead_or_recycled = begin
138
+ (not @excel.alive?)
139
+ rescue WeakRef::RefError => msg
140
+ true
141
+ end
142
+ end
143
+ if (not @excel) || dead_or_recycled
144
+ if options[:excel] == :new || dead_or_recycled
145
+ excel_options = {:displayalerts => false, :visible => false}.merge(options)
146
+ excel_options[:reuse] = false
147
+ @excel = excel_class.new(excel_options)
148
+ else
149
+ @excel = options[:excel].excel
129
150
  end
130
151
  end
131
152
  # if :excel => :new or (:excel => :reuse but could not reuse)
132
153
  # keep the old values for :visible and :displayalerts, set them only if the parameters are given
133
- if (not @excel_options)
134
- @excel.displayalerts = @options[:displayalerts] unless @options[:displayalerts].nil?
135
- @excel.visible = @options[:visible] unless @options[:visible].nil?
154
+ if (not excel_options)
155
+ @excel.displayalerts = options[:displayalerts] unless options[:displayalerts].nil?
156
+ @excel.visible = options[:visible] unless options[:visible].nil?
136
157
  end
137
158
  end
138
159
 
139
- def get_workbook file
160
+ def ensure_workbook(file, options)
140
161
  file = @stored_filename ? @stored_filename : file
141
162
  unless File.exist?(file)
142
- if @options[:if_absent] == :create
163
+ if options[:if_absent] == :create
143
164
  @workbook = excel_class.current.generate_workbook(file)
144
165
  else
145
- raise ExcelErrorOpen, "file #{file} not found"
166
+ raise ExcelErrorOpen, "file #{file.inspect} not found"
146
167
  end
147
168
  end
148
169
  @workbook = @excel.Workbooks.Item(File.basename(file)) rescue nil
@@ -151,104 +172,119 @@ module RobustExcelOle
151
172
  (not (RobustExcelOle::absolute_path(file) == @workbook.Fullname))
152
173
  # if book is obstructed by a book with same name and different path
153
174
  if obstructed_by_other_book then
154
- case @options[:if_obstructed]
175
+ case options[:if_obstructed]
155
176
  when :raise
156
- raise ExcelErrorOpen, "blocked by a book with the same name in a different path: #{File.basename(file)}"
177
+ raise ExcelErrorOpen, "blocked by a book with the same name in a different path: #{File.basename(file).inspect}"
157
178
  when :forget
158
179
  @workbook.Close
159
180
  @workbook = nil
160
- open_or_create_workbook file
181
+ open_or_create_workbook(file, options)
161
182
  when :save
162
183
  save unless @workbook.Saved
163
184
  @workbook.Close
164
185
  @workbook = nil
165
- open_or_create_workbook file
186
+ open_or_create_workbook(file, options)
166
187
  when :close_if_saved
167
188
  if (not @workbook.Saved) then
168
- raise ExcelErrorOpen, "book with the same name in a different path is unsaved: #{File.basename(file)}"
189
+ raise ExcelErrorOpen, "workbook with the same name in a different path is unsaved: #{File.basename(file).inspect}"
169
190
  else
170
191
  @workbook.Close
171
192
  @workbook = nil
172
- open_or_create_workbook file
193
+ open_or_create_workbook(file, options)
173
194
  end
174
195
  when :new_excel
175
- @excel_options = {:displayalerts => false, :visible => false}.merge(@options)
176
- @excel_options[:reuse] = false
177
- @excel = excel_class.new(@excel_options)
178
- open_or_create_workbook file
196
+ excel_options = {:displayalerts => false, :visible => false}.merge(options)
197
+ excel_options[:reuse] = false
198
+ @excel = excel_class.new(excel_options)
199
+ open_or_create_workbook(file, options)
179
200
  else
180
- raise ExcelErrorOpen, ":if_obstructed: invalid option: #{@options[:if_obstructed]}"
201
+ raise ExcelErrorOpen, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}"
181
202
  end
182
203
  else
183
204
  # book open, not obstructed by an other book, but not saved and writable
184
205
  if (not @workbook.Saved) then
185
- case @options[:if_unsaved]
206
+ case options[:if_unsaved]
186
207
  when :raise
187
- raise ExcelErrorOpen, "book is already open but not saved (#{File.basename(file)})"
208
+ raise ExcelErrorOpen, "workbook is already open but not saved: #{File.basename(file).inspect}"
188
209
  when :forget
189
210
  @workbook.Close
190
211
  @workbook = nil
191
- open_or_create_workbook file
212
+ open_or_create_workbook(file, options)
192
213
  when :accept
193
214
  # do nothing
194
215
  when :alert
195
216
  @excel.with_displayalerts true do
196
- open_or_create_workbook file
217
+ open_or_create_workbook(file,options)
197
218
  end
198
219
  when :new_excel
199
- @excel_options = {:displayalerts => false, :visible => false}.merge(@options)
200
- @excel_options[:reuse] = false
201
- @excel = excel_class.new(@excel_options)
202
- open_or_create_workbook file
220
+ excel_options = {:displayalerts => false, :visible => false}.merge(options)
221
+ excel_options[:reuse] = false
222
+ @excel = excel_class.new(excel_options)
223
+ open_or_create_workbook(file, options)
203
224
  else
204
- raise ExcelErrorOpen, ":if_unsaved: invalid option: #{@options[:if_unsaved]}"
225
+ raise ExcelErrorOpen, ":if_unsaved: invalid option: #{options[:if_unsaved].inspect}"
205
226
  end
206
227
  end
207
228
  end
208
229
  else
209
230
  # book is not open
210
- open_or_create_workbook file
231
+ open_or_create_workbook(file, options)
211
232
  end
212
233
  end
213
234
 
214
- def open_or_create_workbook file
215
- if ((not @workbook) || (@options[:if_unsaved] == :alert) || @options[:if_obstructed]) then
235
+ private
236
+
237
+ def open_or_create_workbook(file, options)
238
+ if ((not @workbook) || (options[:if_unsaved] == :alert) || options[:if_obstructed]) then
216
239
  begin
217
240
  filename = RobustExcelOle::absolute_path(file)
218
241
  begin
219
242
  workbooks = @excel.Workbooks
220
243
  rescue RuntimeError => msg
221
- puts "RuntimeError: #{msg.message}"
222
- raise ExcelErrorOpen, "Excel instance not alive or damaged" if msg.message =~ /failed to get Dispatch Interface/
244
+ t "RuntimeError: #{msg.message}"
245
+ if msg.message =~ /method missing: Excel not alive/
246
+ raise ExcelErrorOpen, "Excel instance not alive or damaged"
247
+ else
248
+ raise ExcelErrorOpen, "unknown RuntimeError"
249
+ end
250
+ rescue WeakRef::RefError => msg
251
+ t "WeakRefError: #{msg.message}"
252
+ raise ExcelErrorOpen, "#{msg.message}"
223
253
  end
224
- workbooks.Open(filename,{ 'ReadOnly' => @options[:read_only] })
254
+ workbooks.Open(filename,{ 'ReadOnly' => options[:read_only] })
225
255
  rescue WIN32OLERuntimeError => msg
226
- puts "WIN32OLERuntimeError: #{msg.message}"
227
- raise ExcelErrorOpen, "open: user canceled or open error" if msg.message =~ /800A03EC/
256
+ t "WIN32OLERuntimeError: #{msg.message}"
257
+ if msg.message =~ /800A03EC/
258
+ raise ExcelErrorOpen, "open: user canceled or open error"
259
+ else
260
+ raise ExcelErrorOpen, "unknown WIN32OLERuntimeError"
261
+ end
228
262
  end
229
263
  begin
230
264
  # workaround for bug in Excel 2010: workbook.Open does not always return
231
265
  # the workbook with given file name
232
266
  @workbook = workbooks.Item(File.basename(filename))
233
267
  rescue WIN32OLERuntimeError
234
- raise ExcelErrorOpen, "open: item error"
268
+ raise ExcelErrorOpen, "cannot find the file #{File.basename(filename).inspect}"
235
269
  end
236
270
  end
237
271
  end
238
272
 
239
- # closes the book, if it is alive
273
+ public
274
+
275
+ # closes the workbook, if it is alive
240
276
  #
241
277
  # options:
242
- # :if_unsaved if book is unsaved
243
- # :raise (default) -> raise an exception
244
- # :save -> save the book before it is closed
245
- # :forget -> close the book
246
- # :alert -> give control to excel
278
+ # :if_unsaved if the workbook is unsaved
279
+ # :raise (default) -> raises an exception
280
+ # :save -> saves the workbook before it is closed
281
+ # :forget -> closes the workbook
282
+ # :alert -> gives control to excel
247
283
  def close(opts = {:if_unsaved => :raise})
248
284
  if (alive? && (not @workbook.Saved) && writable) then
249
285
  case opts[:if_unsaved]
250
286
  when :raise
251
- raise ExcelErrorClose, "book is unsaved (#{File.basename(self.stored_filename)})"
287
+ raise ExcelErrorClose, "workbook is unsaved: #{File.basename(self.stored_filename).inspect}"
252
288
  when :save
253
289
  save
254
290
  close_workbook
@@ -259,7 +295,7 @@ module RobustExcelOle
259
295
  close_workbook
260
296
  end
261
297
  else
262
- raise ExcelErrorClose, ":if_unsaved: invalid option: #{opts[:if_unsaved]}"
298
+ raise ExcelErrorClose, ":if_unsaved: invalid option: #{opts[:if_unsaved].inspect}"
263
299
  end
264
300
  else
265
301
  close_workbook
@@ -292,17 +328,17 @@ module RobustExcelOle
292
328
  unobtrusively(*args, &block)
293
329
  end
294
330
 
295
- # modify a book such that its state (open/close, saved/unsaved, readonly/writable) remains unchanged.
331
+ # modifies a workbook such that its state (open/close, saved/unsaved, readonly/writable) remains unchanged.
296
332
  # options:
297
- # :reuse (default) : open closed books in the Excel instance of the book, if it exists, reuse another Excel, otherwise
298
- # :hidden : open closed books in one separate Excel instance that is not visible and has no displayaslerts
299
- # <excel-instance> : open closed books in the given Excel instance
300
- # :read_only: Open the book unobtrusively for reading only (default: false)
301
- # :readonly_excel: if the book is opened only as ReadOnly and shall be modified, then
302
- # true: close it and open it as writable in the excel instance where it was open so far
303
- # false (default) open it as writable in another running excel instance, if it exists,
304
- # otherwise open in a new excel instance.
305
- # :keep_open: let the book open after unobtrusively opening (default: false)
333
+ # :reuse (default) : opens closed workbooks in the Excel instance of the workbook, if it exists, reuse another Excel, otherwise
334
+ # :hidden : opens closed workbooks in one separate Excel instance that is not visible and has no displayaslerts
335
+ # <excel-instance> : opens closed workbooks in the given Excel instance
336
+ # :read_only : opens the workbook unobtrusively for reading only (default: false)
337
+ # :readonly_excel: if the workbook is opened only as ReadOnly and shall be modified, then
338
+ # true: closes it and open it as writable in the Excel instance where it was open so far
339
+ # false (default) opens it as writable in another running excel instance, if it exists,
340
+ # otherwise open in a new Excel instance.
341
+ # :keep_open: lets the workbook open after unobtrusively opening (default: false)
306
342
  def self.unobtrusively(file, if_closed = nil, opts = { }, &block)
307
343
  if if_closed.is_a? Hash
308
344
  opts = if_closed
@@ -316,6 +352,14 @@ module RobustExcelOle
316
352
  }.merge(opts)
317
353
  book = bookstore.fetch(file, :prefer_writable => (not options[:read_only]))
318
354
  was_not_alive_or_nil = book.nil? || (not book.alive?)
355
+ workbook = book.excel.Workbooks.Item(File.basename(file)) rescue nil
356
+ now_alive =
357
+ begin
358
+ workbook.Name
359
+ true
360
+ rescue
361
+ false
362
+ end
319
363
  was_saved = was_not_alive_or_nil ? true : book.saved
320
364
  was_writable = book.writable unless was_not_alive_or_nil
321
365
  begin
@@ -344,25 +388,26 @@ module RobustExcelOle
344
388
  if (not was_not_alive_or_nil) && (not options[:read_only]) && (not was_writable) && options[:readonly_excel]
345
389
  open(file, :force_excel => book.excel, :if_obstructed => :new_excel, :read_only => true)
346
390
  end
347
- book.close if (was_not_alive_or_nil && (not opts[:keep_open]) && book)
391
+ book.close if (was_not_alive_or_nil && (not now_alive) && (not options[:keep_open]) && book)
348
392
  end
349
393
  end
350
394
 
395
+ # reopens a closed workbook
351
396
  def reopen
352
397
  self.class.open(self.stored_filename)
353
398
  end
354
399
 
355
- # rename a range
356
- def rename_range(name,new_name)
400
+ # renames a range
401
+ def rename_range(name, new_name)
357
402
  begin
358
403
  item = self.Names.Item(name)
359
404
  rescue WIN32OLERuntimeError
360
- raise ExcelError, "name #{name} not in #{File.basename(self.stored_filename)}"
405
+ raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
361
406
  end
362
407
  begin
363
408
  item.Name = new_name
364
409
  rescue WIN32OLERuntimeError
365
- raise ExcelError, "name error in #{File.basename(self.stored_filename)}"
410
+ raise ExcelError, "name error in #{File.basename(self.stored_filename).inspect}"
366
411
  end
367
412
  end
368
413
 
@@ -374,41 +419,64 @@ module RobustExcelOle
374
419
  item = self.Names.Item(name)
375
420
  rescue WIN32OLERuntimeError
376
421
  return opts[:default] if opts[:default]
377
- raise ExcelErrorNValue, "name #{name} not in #{File.basename(self.stored_filename)}"
422
+ raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
378
423
  end
379
424
  begin
380
425
  value = item.RefersToRange.Value
381
426
  rescue WIN32OLERuntimeError
382
- return opts[:default] if opts[:default]
383
- raise ExcelErrorNValue, "RefersToRange error of name #{name} in #{File.basename(self.stored_filename)}"
427
+ begin
428
+ sheet = self[0]
429
+ value = sheet.Evaluate(name)
430
+ rescue WIN32OLERuntimeError
431
+ return opts[:default] if opts[:default]
432
+ raise SheetError, "cannot evaluate name #{name.inspect} in sheet"
433
+ end
384
434
  end
385
- value
435
+ if value == -2146826259
436
+ return opts[:default] if opts[:default]
437
+ raise SheetError, "cannot evaluate name #{name.inspect} in sheet"
438
+ end
439
+ return opts[:default] if (value.nil? && opts[:default])
440
+ value
386
441
  end
387
442
 
388
443
  # set the contents of a range with given name
389
- def set_nvalue(name,value)
444
+ def set_nvalue(name, value)
390
445
  begin
391
446
  item = self.Names.Item(name)
392
447
  rescue WIN32OLERuntimeError
393
- raise ExcelErrorNValue, "name #{name} not in #{File.basename(self.stored_filename)}"
448
+ raise ExcelError, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
394
449
  end
395
450
  begin
396
451
  item.RefersToRange.Value = value
397
452
  rescue WIN32OLERuntimeError
398
- raise ExcelErrorNValue, "RefersToRange error of name #{name} in #{File.basename(self.stored_filename)}"
453
+ raise ExcelError, "RefersToRange error of name #{name.inspect} in #{File.basename(self.stored_filename).inspect}"
399
454
  end
400
455
  end
401
456
 
402
- def activate
457
+ # brings the workbook to the foreground and available for heyboard inputs, and makes the Excel instance visible
458
+ def activate
403
459
  @excel.visible = true
404
460
  begin
405
- self.Activate
406
- self.ActiveSheet.Activate
461
+ Win32API.new("user32","SetForegroundWindow","I","I").call(@excel.hwnd) # Excel 2010
462
+ @workbook.Activate # Excel 2007
407
463
  rescue WIN32OLERuntimeError
408
464
  raise ExcelError, "cannot activate"
409
465
  end
410
466
  end
411
467
 
468
+ # returns whether the workbook is visible or invisible
469
+ def visible
470
+ @excel.Windows(@workbook.Name).Visible
471
+ end
472
+
473
+ # makes a workbook visible or invisible
474
+ def visible= visible_value
475
+ saved = @workbook.Saved
476
+ @excel.Windows(@workbook.Name).Visible = visible_value
477
+ save if saved
478
+ end
479
+
412
480
  # returns true, if the workbook reacts to methods, false otherwise
413
481
  def alive?
414
482
  begin
@@ -416,7 +484,7 @@ module RobustExcelOle
416
484
  true
417
485
  rescue
418
486
  @workbook = nil # dead object won't be alive again
419
- #puts $!.message
487
+ #t $!.message
420
488
  false
421
489
  end
422
490
  end
@@ -434,71 +502,108 @@ module RobustExcelOle
434
502
  @workbook.Saved if @workbook
435
503
  end
436
504
 
437
- # returns true, if the full book names and excel appications are identical, false otherwise
505
+ # returns true, if the full book names and excel Instances are identical, false otherwise
438
506
  def == other_book
439
507
  other_book.is_a?(Book) &&
440
508
  @excel == other_book.excel &&
441
509
  self.filename == other_book.filename
442
510
  end
443
-
444
- # saves a book.
445
- # returns true, if successfully saved, nil otherwise
446
- def save
447
- raise ExcelErrorSave, "Not opened for writing (opened with :read_only option)" if @options[:read_only]
448
- if @workbook then
449
- begin
450
- @workbook.Save
451
- rescue WIN32OLERuntimeError => msg
452
- if msg.message =~ /SaveAs/ and msg.message =~ /Workbook/ then
453
- raise ExcelErrorSave, "workbook not saved"
454
- else
455
- raise ExcelErrorSaveUnknown, "unknown WIN32OELERuntimeError:\n#{msg.message}"
456
- end
457
- end
458
- true
459
- else
460
- nil
511
+
512
+ def self.books
513
+ bookstore.books
514
+ end
515
+
516
+ # simple save of a workbook.
517
+ # returns true, if successfully saved, nil or error otherwise
518
+ def save
519
+ raise ExcelErrorSave, "Workbook is not alive" if (not alive?)
520
+ raise ExcelErrorSave, "Not opened for writing (opened with :read_only option)" if @workbook.ReadOnly
521
+ begin
522
+ @workbook.Save
523
+ rescue WIN32OLERuntimeError => msg
524
+ if msg.message =~ /SaveAs/ and msg.message =~ /Workbook/ then
525
+ raise ExcelErrorSave, "workbook not saved"
526
+ else
527
+ raise ExcelErrorSaveUnknown, "unknown WIN32OELERuntimeError:\n#{msg.message}"
528
+ end
461
529
  end
530
+ true
462
531
  end
463
532
 
464
- # saves a book.
533
+ # saves a workbook with a given file name.
465
534
  #
466
535
  # options:
467
536
  # :if_exists if a file with the same name exists, then
468
- # :raise -> raise an exception, dont't write the file (default)
469
- # :overwrite -> write the file, delete the old file
470
- # :alert -> give control to Excel
471
- # returns true, if successfully saved, nil otherwise
472
- def save_as(file = nil, opts = {:if_exists => :raise} )
473
- raise IOError, "Not opened for writing(open with :read_only option)" if @options[:read_only]
474
- @opts = opts
537
+ # :raise -> raises an exception, dont't write the file (default)
538
+ # :overwrite -> writes the file, delete the old file
539
+ # :alert -> gives control to Excel
540
+ # :if_obstructed if a workbook with the same name and different path is already open and blocks the saving, then
541
+ # :raise (default) -> raises an exception
542
+ # :forget -> closes the blocking workbook
543
+ # :save -> saves the blocking workbook and closes it
544
+ # :close_if_saved -> closes the blocking workbook, if it is saved,
545
+ # otherwise raises an exception
546
+ # returns true, if successfully saved, nil or error otherwise
547
+ def save_as(file = nil, opts = { } )
548
+ raise ExcelErrorSave, "Workbook is not alive" if (not alive?)
549
+ raise ExcelErrorSave, "Not opened for writing (opened with :read_only option)" if @workbook.ReadOnly
550
+ options = {
551
+ :if_exists => :raise,
552
+ :if_obstructed => :raise,
553
+ }.merge(opts)
475
554
  if File.exist?(file) then
476
- case @opts[:if_exists]
555
+ case options[:if_exists]
477
556
  when :overwrite
478
- begin
479
- File.delete(file)
480
- rescue Errno::EACCES
481
- raise ExcelErrorSave, "book is open and used in Excel"
557
+ if file == self.filename
558
+ save
559
+ return
560
+ else
561
+ begin
562
+ File.delete(file)
563
+ rescue Errno::EACCES
564
+ raise ExcelErrorSave, "workbook is open and used in Excel"
565
+ end
482
566
  end
483
- save_as_workbook(file)
484
567
  when :alert
485
568
  @excel.with_displayalerts true do
486
- save_as_workbook(file)
569
+ save_as_workbook(file, options)
487
570
  end
571
+ true
572
+ return
488
573
  when :raise
489
- raise ExcelErrorSave, "book already exists: #{File.basename(file)}"
574
+ raise ExcelErrorSave, "file already exists: #{File.basename(file).inspect}"
490
575
  else
491
- raise ExcelErrorSave, ":if_exists: invalid option: #{@opts[:if_exists]}"
576
+ raise ExcelErrorSave, ":if_exists: invalid option: #{options[:if_exists].inspect}"
492
577
  end
493
- else
494
- save_as_workbook(file)
495
578
  end
579
+ blocking_workbook =
580
+ begin
581
+ @excel.Workbooks.Item(File.basename(file))
582
+ rescue WIN32OLERuntimeError => msg
583
+ nil
584
+ end
585
+ if blocking_workbook then
586
+ case options[:if_obstructed]
587
+ when :raise
588
+ raise ExcelErrorSave, "blocked by another workbook: #{File.basename(file).inspect}"
589
+ when :forget
590
+ # nothing
591
+ when :save
592
+ blocking_workbook.Save
593
+ when :close_if_saved
594
+ raise ExcelErrorSave, "blocking workbook is unsaved: #{File.basename(file).inspect}" unless blocking_workbook.Saved
595
+ else
596
+ raise ExcelErrorSave, ":if_obstructed: invalid option: #{options[:if_obstructed].inspect}"
597
+ end
598
+ blocking_workbook.Close
599
+ end
600
+ save_as_workbook(file, options)
496
601
  true
497
602
  end
498
603
 
499
604
  private
500
605
 
501
- def save_as_workbook(file)
606
+ def save_as_workbook(file, options)
502
607
  begin
503
608
  dirname, basename = File.split(file)
504
609
  file_format =
@@ -511,7 +616,7 @@ module RobustExcelOle
511
616
  bookstore.store(self)
512
617
  rescue WIN32OLERuntimeError => msg
513
618
  if msg.message =~ /SaveAs/ and msg.message =~ /Workbook/ then
514
- if @opts[:if_exists] == :alert then
619
+ if options[:if_exists] == :alert then
515
620
  raise ExcelErrorSave, "not saved or canceled by user"
516
621
  else
517
622
  return nil
@@ -530,24 +635,24 @@ module RobustExcelOle
530
635
  def [] name
531
636
  name += 1 if name.is_a? Numeric
532
637
  begin
533
- RobustExcelOle::Sheet.new(@workbook.Worksheets.Item(name))
638
+ sheet_class.new(@workbook.Worksheets.Item(name))
534
639
  rescue WIN32OLERuntimeError => msg
535
640
  if msg.message =~ /8002000B/
536
641
  nvalue(name)
537
642
  else
538
- raise ExcelError, "could neither return a sheet nor a value of a range when giving the name #{name}"
643
+ raise ExcelError, "could neither return a sheet nor a value of a range when giving the name #{name.inspect}"
539
644
  end
540
645
  end
541
646
  end
542
647
 
543
- # set the value of a range given its name
648
+ # sets the value of a range given its name
544
649
  def []= (name, value)
545
650
  set_nvalue(name,value)
546
651
  end
547
652
 
548
653
  def each
549
654
  @workbook.Worksheets.each do |sheet|
550
- yield RobustExcelOle::Sheet.new(sheet)
655
+ yield sheet_class.new(sheet)
551
656
  end
552
657
  end
553
658
 
@@ -558,17 +663,17 @@ module RobustExcelOle
558
663
  end
559
664
  new_sheet_name = opts.delete(:as)
560
665
  ws = @workbook.Worksheets
561
- after_or_before, base_sheet = opts.to_a.first || [:after, Sheet.new(ws.Item(ws.Count))]
666
+ after_or_before, base_sheet = opts.to_a.first || [:after, sheet_class.new(ws.Item(ws.Count))]
562
667
  base_sheet = base_sheet.worksheet
563
668
  sheet ? sheet.Copy({ after_or_before.to_s => base_sheet }) : @workbook.WorkSheets.Add({ after_or_before.to_s => base_sheet })
564
- new_sheet = RobustExcelOle::Sheet.new(@excel.Activesheet)
669
+ new_sheet = sheet_class.new(@excel.Activesheet)
565
670
  begin
566
671
  new_sheet.name = new_sheet_name if new_sheet_name
567
672
  rescue WIN32OLERuntimeError => msg
568
673
  if msg.message =~ /800A03EC/
569
674
  raise ExcelErrorSheet, "sheet name already exists"
570
675
  else
571
- puts "#{msg.message}"
676
+ t "#{msg.message}"
572
677
  raise ExcelErrorSheetUnknown
573
678
  end
574
679
  end
@@ -583,12 +688,42 @@ module RobustExcelOle
583
688
  self.class.bookstore
584
689
  end
585
690
 
691
+ def self.show_books
692
+ bookstore.books
693
+ end
694
+
586
695
  def to_s
587
- "<#BOOK:" + "#{"not alive " unless alive?}" + "#{File.basename(@stored_filename)}" + " #{@workbook} #{@excel}" + ">"
696
+ "#{self.filename}"
588
697
  end
589
698
 
590
699
  def inspect
591
- self.to_s
700
+ "<#Book: " + "#{"not alive " unless alive?}" + "#{File.basename(self.filename) if alive?}" + " #{@workbook} #{@excel}" + ">"
701
+ end
702
+
703
+ def self.excel_class
704
+ @excel_class ||= begin
705
+ module_name = self.parent_name
706
+ "#{module_name}::Excel".constantize
707
+ rescue NameError => e
708
+ Excel
709
+ end
710
+ end
711
+
712
+ def self.sheet_class
713
+ @sheet_class ||= begin
714
+ module_name = self.parent_name
715
+ "#{module_name}::Sheet".constantize
716
+ rescue NameError => e
717
+ Sheet
718
+ end
719
+ end
720
+
721
+ def excel_class
722
+ self.class.excel_class
723
+ end
724
+
725
+ def sheet_class
726
+ self.class.sheet_class
592
727
  end
593
728
 
594
729
  private
@@ -596,10 +731,11 @@ module RobustExcelOle
596
731
  def method_missing(name, *args)
597
732
  if name.to_s[0,1] =~ /[A-Z]/
598
733
  begin
734
+ raise ExcelError, "method missing: workbook not alive" unless alive?
599
735
  @workbook.send(name, *args)
600
736
  rescue WIN32OLERuntimeError => msg
601
737
  if msg.message =~ /unknown property or method/
602
- raise VBAMethodMissingError, "unknown VBA property or method #{name}"
738
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
603
739
  else
604
740
  raise msg
605
741
  end
@@ -631,9 +767,6 @@ public
631
767
  class ExcelErrorSaveUnknown < ExcelErrorSave # :nodoc: #
632
768
  end
633
769
 
634
- class ExcelErrorNValue < ExcelError # :nodoc: #
635
- end
636
-
637
770
  class ExcelUserCanceled < RuntimeError # :nodoc: #
638
771
  end
639
772
 
@@ -642,4 +775,5 @@ public
642
775
 
643
776
  class ExcelErrorSheetUnknown < ExcelErrorSheet # :nodoc: #
644
777
  end
645
- end
778
+
779
+ end