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.
- 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
|