robust_excel_ole 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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