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.
- data/Changelog +33 -0
- data/README.rdoc +97 -35
- data/README_detail.rdoc +121 -53
- data/examples/edit_sheets/example_naming.rb +1 -0
- data/lib/reo_console.rb +52 -0
- data/lib/robust_excel_ole.rb +41 -7
- data/lib/robust_excel_ole/book.rb +327 -193
- data/lib/robust_excel_ole/bookstore.rb +33 -17
- data/lib/robust_excel_ole/excel.rb +280 -188
- data/lib/robust_excel_ole/sheet.rb +29 -16
- data/lib/robust_excel_ole/version.rb +1 -1
- data/lib/spec_helper.rb +1 -1
- data/reo.bat +1 -1
- data/spec/book_specs/book_all_spec.rb +22 -0
- data/spec/{book_close_spec.rb → book_specs/book_close_spec.rb} +14 -14
- data/spec/{book_misc_spec.rb → book_specs/book_misc_spec.rb} +38 -29
- data/spec/{book_open_spec.rb → book_specs/book_open_spec.rb} +40 -16
- data/spec/{book_save_spec.rb → book_specs/book_save_spec.rb} +173 -12
- data/spec/{book_sheet_spec.rb → book_specs/book_sheet_spec.rb} +8 -4
- data/spec/{book_spec.rb → book_specs/book_spec.rb} +456 -187
- data/spec/{book_subclass_spec.rb → book_specs/book_subclass_spec.rb} +4 -4
- data/spec/{book_unobtr_spec.rb → book_specs/book_unobtr_spec.rb} +64 -4
- data/spec/bookstore_spec.rb +11 -4
- data/spec/cell_spec.rb +7 -5
- data/spec/data/another_workbook.xls +0 -0
- data/spec/data/different_workbook.xls +0 -0
- data/spec/data/more_data/workbook.xls +0 -0
- data/spec/data/workbook.xls +0 -0
- data/spec/excel_spec.rb +367 -87
- data/spec/range_spec.rb +6 -4
- data/spec/robust_excel_ole_spec.rb +113 -0
- data/spec/sheet_spec.rb +42 -6
- metadata +16 -12
@@ -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
|
data/lib/reo_console.rb
ADDED
@@ -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"
|
data/lib/robust_excel_ole.rb
CHANGED
@@ -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
|
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
|
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
|
26
|
+
# opens a workbook.
|
27
27
|
#
|
28
|
-
# when reopening a
|
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
|
33
|
-
# Otherwise, i.e. if the
|
34
|
-
# :reuse (default) ->
|
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
|
37
|
-
# :new ->
|
38
|
-
# <excel-instance> ->
|
39
|
-
# :force_excel no matter whether the
|
40
|
-
# :new (default) ->
|
41
|
-
# <excel-instance> ->
|
42
|
-
# :if_unsaved if an unsaved
|
43
|
-
# :raise (default)
|
44
|
-
# :forget -> close the unsaved
|
45
|
-
# :accept ->
|
46
|
-
# :alert ->
|
47
|
-
# :new_excel ->
|
48
|
-
# :if_obstructed if a
|
49
|
-
# :raise (default) ->
|
50
|
-
# :forget ->
|
51
|
-
# :save ->
|
52
|
-
# :close_if_saved ->
|
53
|
-
# otherwise
|
54
|
-
# :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
|
59
|
-
# :displayalerts
|
60
|
-
# :visible
|
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
|
-
|
64
|
+
options = DEFAULT_OPEN_OPTS.merge(opts)
|
65
65
|
book = nil
|
66
|
-
if (not (
|
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
|
70
|
-
:prefer_excel => (
|
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
|
73
|
-
(not (book.alive? && (not book.saved) && (not
|
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.
|
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
|
78
|
-
#
|
79
|
-
book.
|
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
|
-
|
85
|
-
new(file,
|
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
|
104
|
-
|
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
|
-
|
122
|
-
if
|
123
|
-
|
124
|
-
@
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
134
|
-
@excel.displayalerts =
|
135
|
-
@excel.visible =
|
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
|
160
|
+
def ensure_workbook(file, options)
|
140
161
|
file = @stored_filename ? @stored_filename : file
|
141
162
|
unless File.exist?(file)
|
142
|
-
if
|
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
|
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
|
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
|
186
|
+
open_or_create_workbook(file, options)
|
166
187
|
when :close_if_saved
|
167
188
|
if (not @workbook.Saved) then
|
168
|
-
raise ExcelErrorOpen, "
|
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
|
193
|
+
open_or_create_workbook(file, options)
|
173
194
|
end
|
174
195
|
when :new_excel
|
175
|
-
|
176
|
-
|
177
|
-
@excel = excel_class.new(
|
178
|
-
open_or_create_workbook
|
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: #{
|
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
|
206
|
+
case options[:if_unsaved]
|
186
207
|
when :raise
|
187
|
-
raise ExcelErrorOpen, "
|
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
|
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
|
217
|
+
open_or_create_workbook(file,options)
|
197
218
|
end
|
198
219
|
when :new_excel
|
199
|
-
|
200
|
-
|
201
|
-
@excel = excel_class.new(
|
202
|
-
open_or_create_workbook
|
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: #{
|
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
|
231
|
+
open_or_create_workbook(file, options)
|
211
232
|
end
|
212
233
|
end
|
213
234
|
|
214
|
-
|
215
|
-
|
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
|
-
|
222
|
-
|
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' =>
|
254
|
+
workbooks.Open(filename,{ 'ReadOnly' => options[:read_only] })
|
225
255
|
rescue WIN32OLERuntimeError => msg
|
226
|
-
|
227
|
-
|
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, "
|
268
|
+
raise ExcelErrorOpen, "cannot find the file #{File.basename(filename).inspect}"
|
235
269
|
end
|
236
270
|
end
|
237
271
|
end
|
238
272
|
|
239
|
-
|
273
|
+
public
|
274
|
+
|
275
|
+
# closes the workbook, if it is alive
|
240
276
|
#
|
241
277
|
# options:
|
242
|
-
# :if_unsaved if
|
243
|
-
# :raise (default) ->
|
244
|
-
# :save ->
|
245
|
-
# :forget ->
|
246
|
-
# :alert ->
|
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, "
|
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
|
-
#
|
331
|
+
# modifies a workbook such that its state (open/close, saved/unsaved, readonly/writable) remains unchanged.
|
296
332
|
# options:
|
297
|
-
# :reuse (default) :
|
298
|
-
# :hidden :
|
299
|
-
# <excel-instance> :
|
300
|
-
# :read_only:
|
301
|
-
# :readonly_excel: if the
|
302
|
-
# true:
|
303
|
-
# false (default)
|
304
|
-
# otherwise open in a new
|
305
|
-
# :keep_open:
|
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
|
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
|
-
#
|
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
|
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
|
-
|
383
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
406
|
-
|
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
|
-
#
|
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
|
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
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
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
|
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 ->
|
469
|
-
# :overwrite ->
|
470
|
-
# :alert ->
|
471
|
-
#
|
472
|
-
|
473
|
-
|
474
|
-
|
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
|
555
|
+
case options[:if_exists]
|
477
556
|
when :overwrite
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
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, "
|
574
|
+
raise ExcelErrorSave, "file already exists: #{File.basename(file).inspect}"
|
490
575
|
else
|
491
|
-
raise ExcelErrorSave, ":if_exists: invalid option: #{
|
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
|
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
|
-
|
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
|
-
#
|
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
|
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,
|
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 =
|
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
|
-
|
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
|
-
"
|
696
|
+
"#{self.filename}"
|
588
697
|
end
|
589
698
|
|
590
699
|
def inspect
|
591
|
-
self.
|
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
|
-
|
778
|
+
|
779
|
+
end
|