robust_excel_ole 1.31 → 1.32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog +20 -1
- data/README.rdoc +118 -18
- data/___dummy_workbook.xls +0 -0
- data/benchmarking/creek_example.rb +1 -1
- data/benchmarking/roo_example.rb +1 -1
- data/benchmarking/simple_xlsx_reader_example.rb +1 -1
- data/benchmarking/spreadsheet_example.rb +1 -1
- data/docs/README_excel.rdoc +16 -24
- data/docs/README_listobjects.rdoc +176 -0
- data/docs/README_open.rdoc +12 -12
- data/docs/README_ranges.rdoc +72 -55
- data/docs/README_save_close.rdoc +3 -3
- data/docs/README_sheet.rdoc +18 -13
- data/examples/example_ruby_library.rb +2 -2
- data/examples/introductory_examples/example_range.rb +2 -2
- data/examples/modifying_sheets/example_access_sheets_and_cells.rb +6 -6
- data/examples/modifying_sheets/example_add_names.rb +1 -1
- data/examples/modifying_sheets/example_concating.rb +1 -1
- data/examples/modifying_sheets/example_copying.rb +2 -2
- data/examples/modifying_sheets/example_listobjects.rb +86 -0
- data/examples/modifying_sheets/example_naming.rb +1 -1
- data/examples/modifying_sheets/example_ranges.rb +1 -1
- data/examples/open_save_close/example_control_to_excel.rb +1 -1
- data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
- data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
- data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
- data/examples/open_save_close/example_if_unsaved_forget.rb +3 -3
- data/examples/open_save_close/example_if_unsaved_forget_more.rb +4 -4
- data/examples/open_save_close/example_read_only.rb +1 -1
- data/examples/open_save_close/example_simple.rb +1 -1
- data/examples/open_save_close/example_unobtrusively.rb +3 -3
- data/lib/robust_excel_ole/address_tool.rb +54 -44
- data/lib/robust_excel_ole/base.rb +4 -6
- data/lib/robust_excel_ole/bookstore.rb +2 -16
- data/lib/robust_excel_ole/cell.rb +16 -21
- data/lib/robust_excel_ole/excel.rb +131 -186
- data/lib/robust_excel_ole/general.rb +82 -55
- data/lib/robust_excel_ole/list_object.rb +182 -109
- data/lib/robust_excel_ole/list_row.rb +65 -38
- data/lib/robust_excel_ole/range.rb +125 -93
- data/lib/robust_excel_ole/range_owners.rb +52 -66
- data/lib/robust_excel_ole/version.rb +1 -1
- data/lib/robust_excel_ole/workbook.rb +168 -176
- data/lib/robust_excel_ole/worksheet.rb +177 -141
- data/robust_excel_ole.gemspec +4 -3
- data/spec/bookstore_spec.rb +2 -3
- data/spec/cell_spec.rb +9 -9
- data/spec/data/more_data/workbook.xls +0 -0
- data/spec/excel_spec.rb +132 -85
- data/spec/general_spec.rb +47 -15
- data/spec/list_object_spec.rb +258 -145
- data/spec/list_row_spec.rb +218 -0
- data/spec/range_spec.rb +76 -29
- data/spec/spec_helper.rb +15 -1
- data/spec/workbook_spec.rb +75 -34
- data/spec/workbook_specs/workbook_all_spec.rb +2 -1
- data/spec/workbook_specs/workbook_misc_spec.rb +20 -13
- data/spec/workbook_specs/workbook_open_spec.rb +47 -45
- data/spec/workbook_specs/workbook_save_spec.rb +21 -22
- data/spec/workbook_specs/workbook_sheet_spec.rb +3 -3
- data/spec/workbook_specs/workbook_unobtr_spec.rb +303 -303
- data/spec/worksheet_spec.rb +522 -318
- metadata +37 -2
@@ -14,17 +14,15 @@ module RobustExcelOle
|
|
14
14
|
# @param [Hash] opts the options
|
15
15
|
# @option opts [Symbol] :default the default value that is provided if no contents could be returned
|
16
16
|
# @return [Variant] the contents of a range with given name
|
17
|
-
def namevalue_global(name, opts = { :
|
17
|
+
def namevalue_global(name, opts = { default: :__not_provided })
|
18
18
|
name_obj = begin
|
19
|
-
|
19
|
+
get_name_object(name)
|
20
20
|
rescue NameNotFound => msg
|
21
|
-
return opts[:default] unless opts[:default] == :__not_provided
|
22
21
|
raise
|
23
22
|
end
|
24
23
|
ole_range = name_obj.RefersToRange
|
25
24
|
worksheet = self if self.is_a?(Worksheet)
|
26
25
|
value = begin
|
27
|
-
#name_obj.RefersToRange.Value
|
28
26
|
if !::RANGES_JRUBY_BUG
|
29
27
|
ole_range.Value
|
30
28
|
else
|
@@ -33,11 +31,9 @@ module RobustExcelOle
|
|
33
31
|
end
|
34
32
|
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
|
35
33
|
sheet = if self.is_a?(Worksheet) then self
|
36
|
-
|
37
|
-
elsif self.is_a?(Excel) then self.workbook.sheet(1)
|
34
|
+
elsif self.is_a?(Workbook) then self.sheet(1)
|
38
35
|
end
|
39
36
|
begin
|
40
|
-
#sheet.Evaluate(name_obj.Name).Value
|
41
37
|
# does it result in a range?
|
42
38
|
ole_range = sheet.Evaluate(name_obj.Name)
|
43
39
|
if !::RANGES_JRUBY_BUG
|
@@ -64,33 +60,31 @@ module RobustExcelOle
|
|
64
60
|
# @param [Variant] value the contents of the range
|
65
61
|
# @option opts [Symbol] :color the color of the range when set
|
66
62
|
def set_namevalue_global(name, value, opts = { })
|
67
|
-
begin
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value )
|
83
|
-
end
|
63
|
+
name_obj = begin
|
64
|
+
get_name_object(name)
|
65
|
+
rescue NameNotFound => msg
|
66
|
+
raise
|
67
|
+
end
|
68
|
+
ole_range = name_obj.RefersToRange
|
69
|
+
ole_range.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
|
70
|
+
if !::RANGES_JRUBY_BUG
|
71
|
+
ole_range.Value = value
|
72
|
+
else
|
73
|
+
address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
|
74
|
+
row, col = address_tool.as_integer_ranges(address_r1c1)
|
75
|
+
row.each_with_index do |r,i|
|
76
|
+
col.each_with_index do |c,j|
|
77
|
+
ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:pop) ? value[i][j] : value )
|
84
78
|
end
|
85
79
|
end
|
86
|
-
value
|
87
|
-
rescue #WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
|
88
|
-
raise RangeNotEvaluatable, "cannot assign value to range named #{name.inspect} in #{self.inspect}"
|
89
80
|
end
|
81
|
+
value
|
82
|
+
rescue #WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
|
83
|
+
raise RangeNotEvaluatable, "cannot assign value to range named #{name.inspect} in #{self.inspect}\n#{$!.message}"
|
90
84
|
end
|
91
85
|
|
92
86
|
# @private
|
93
|
-
def nameval(name, opts = { :
|
87
|
+
def nameval(name, opts = { default: :__not_provided }) # :deprecated: #
|
94
88
|
namevalue_global(name, opts)
|
95
89
|
end
|
96
90
|
|
@@ -100,7 +94,7 @@ module RobustExcelOle
|
|
100
94
|
end
|
101
95
|
|
102
96
|
# @private
|
103
|
-
def rangeval(name, opts = { :
|
97
|
+
def rangeval(name, opts = { default: :__not_provided }) # :deprecated: #
|
104
98
|
namevalue(name, opts)
|
105
99
|
end
|
106
100
|
|
@@ -125,53 +119,45 @@ module RobustExcelOle
|
|
125
119
|
# @params [Address] address of the range
|
126
120
|
def add_name(name, addr, addr_deprecated = :__not_provided)
|
127
121
|
addr = [addr,addr_deprecated] unless addr_deprecated == :__not_provided
|
128
|
-
|
129
|
-
self.Names.Add(name, nil, true, nil, nil, nil, nil, nil, nil, '=' + address_tool.as_r1c1(addr))
|
130
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
|
131
|
-
raise RangeNotEvaluatable, "cannot add name #{name.inspect} to range #{addr.inspect}"
|
132
|
-
end
|
122
|
+
self.Names.Add(name, nil, true, nil, nil, nil, nil, nil, nil, '=' + address_tool.as_r1c1(addr))
|
133
123
|
name
|
124
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
|
125
|
+
raise RangeNotEvaluatable, "cannot add name #{name.inspect} to range #{addr.inspect}\n#{$!.message}"
|
134
126
|
end
|
135
127
|
|
136
|
-
|
137
|
-
|
128
|
+
alias set_name add_name # :deprecated :#
|
129
|
+
|
130
|
+
# renames an Excel object
|
131
|
+
# @param [String] old_name the previous name of the Excel object
|
132
|
+
# @param [String] new_name the new name of the Excel object
|
133
|
+
def rename_name(old_name, new_name)
|
134
|
+
item = get_name_object(old_name)
|
135
|
+
item.Name = new_name
|
136
|
+
rescue RobustExcelOle::NameNotFound
|
137
|
+
raise
|
138
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
139
|
+
raise UnexpectedREOError, "name error with name #{old_name.inspect} in #{File.basename(self.stored_filename).inspect}\n#{$!.message}"
|
138
140
|
end
|
139
141
|
|
140
|
-
#
|
141
|
-
# @param [String] name the previous range name
|
142
|
-
# @param [String] new_name the new range name
|
143
|
-
def rename_range(name, new_name)
|
144
|
-
begin
|
145
|
-
item = self.Names.Item(name)
|
146
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
147
|
-
raise NameNotFound, "name #{name.inspect} not in #{File.basename(self.stored_filename).inspect}"
|
148
|
-
end
|
149
|
-
begin
|
150
|
-
item.Name = new_name
|
151
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
152
|
-
raise UnexpectedREOError, "name error in #{File.basename(self.stored_filename).inspect}"
|
153
|
-
end
|
154
|
-
end
|
142
|
+
alias rename_range rename_name # :deprecated :#
|
155
143
|
|
156
|
-
# deletes a name of
|
157
|
-
# @param [String] name
|
158
|
-
# @param [String] new_name the new range name
|
144
|
+
# deletes a name of an Excel object
|
145
|
+
# @param [String] name the name of the Excel object
|
159
146
|
def delete_name(name)
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
end
|
147
|
+
item = get_name_object(name)
|
148
|
+
item.Delete
|
149
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
|
150
|
+
raise UnexpectedREOError, "name error with name #{name.inspect} in #{File.basename(self.stored_filename).inspect}\n#{$!.message}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# @return [Array] defined names
|
154
|
+
def names
|
155
|
+
self.Names.to_a.map(&:name)
|
156
|
+
end
|
171
157
|
|
172
158
|
private
|
173
159
|
|
174
|
-
def
|
160
|
+
def get_name_object(name)
|
175
161
|
self.Names.Item(name)
|
176
162
|
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException, VBAMethodMissingError
|
177
163
|
begin
|
@@ -11,6 +11,8 @@ module RobustExcelOle
|
|
11
11
|
|
12
12
|
class Workbook < RangeOwners
|
13
13
|
|
14
|
+
include Enumerable
|
15
|
+
|
14
16
|
attr_reader :ole_workbook
|
15
17
|
attr_reader :excel
|
16
18
|
attr_reader :stored_filename
|
@@ -20,16 +22,16 @@ module RobustExcelOle
|
|
20
22
|
using ToReoRefinement
|
21
23
|
|
22
24
|
CORE_DEFAULT_OPEN_OPTS = {
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
25
|
+
default: {excel: :current},
|
26
|
+
force: {},
|
27
|
+
update_links: :never
|
26
28
|
}.freeze
|
27
29
|
|
28
30
|
DEFAULT_OPEN_OPTS = {
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
:
|
31
|
+
if_unsaved: :raise,
|
32
|
+
if_obstructed: :raise,
|
33
|
+
if_absent: :raise,
|
34
|
+
if_exists: :raise
|
33
35
|
}.merge(CORE_DEFAULT_OPEN_OPTS).freeze
|
34
36
|
|
35
37
|
ABBREVIATIONS = [
|
@@ -89,7 +91,7 @@ module RobustExcelOle
|
|
89
91
|
process_options(opts)
|
90
92
|
case file_or_workbook
|
91
93
|
when NilClass
|
92
|
-
raise FileNameNotGiven,
|
94
|
+
raise FileNameNotGiven, "filename is nil"
|
93
95
|
when WIN32OLE
|
94
96
|
file = file_or_workbook.Fullname.tr('\\','/')
|
95
97
|
when Workbook
|
@@ -101,7 +103,7 @@ module RobustExcelOle
|
|
101
103
|
file = file_or_workbook.to_path
|
102
104
|
raise FileNotFound, "file #{General.absolute_path(file).inspect} is a directory" if File.directory?(file)
|
103
105
|
else
|
104
|
-
raise TypeREOError,
|
106
|
+
raise TypeREOError, "given object is neither a filename, a Win32ole, nor a Workbook object"
|
105
107
|
end
|
106
108
|
# try to fetch the workbook from the bookstore
|
107
109
|
set_was_open opts, file_or_workbook.is_a?(WIN32OLE)
|
@@ -110,14 +112,14 @@ module RobustExcelOle
|
|
110
112
|
# if readonly is true, then prefer a book that is given in force_excel if this option is set
|
111
113
|
forced_excel = begin
|
112
114
|
(opts[:force][:excel].nil? || opts[:force][:excel] == :current) ?
|
113
|
-
(excel_class.new(:
|
115
|
+
(excel_class.new(reuse: true) if !::CONNECT_JRUBY_BUG) : opts[:force][:excel].to_reo.excel
|
114
116
|
rescue NoMethodError
|
115
117
|
raise TypeREOError, "provided Excel option value is neither an Excel object nor a valid option"
|
116
118
|
end
|
117
119
|
begin
|
118
120
|
book = if File.exists?(file)
|
119
|
-
bookstore.fetch(file, :
|
120
|
-
:
|
121
|
+
bookstore.fetch(file, prefer_writable: !(opts[:read_only]),
|
122
|
+
prefer_excel: (opts[:read_only] ? forced_excel : nil))
|
121
123
|
end
|
122
124
|
rescue
|
123
125
|
raise
|
@@ -153,7 +155,7 @@ module RobustExcelOle
|
|
153
155
|
ole_excel = begin
|
154
156
|
@ole_workbook.Application
|
155
157
|
rescue WIN32OLERuntimeError
|
156
|
-
raise ExcelREOError,
|
158
|
+
raise ExcelREOError, "could not determine the Excel instance\n#{$!.message}"
|
157
159
|
end
|
158
160
|
@excel = excel_class.new(ole_excel)
|
159
161
|
filename = @ole_workbook.Fullname.tr('\\','/')
|
@@ -182,7 +184,7 @@ module RobustExcelOle
|
|
182
184
|
self.class.set_was_open(hash, value)
|
183
185
|
end
|
184
186
|
|
185
|
-
def self.process_options(opts, proc_opts = {:
|
187
|
+
def self.process_options(opts, proc_opts = {use_defaults: true})
|
186
188
|
translate(opts)
|
187
189
|
default_opts = (proc_opts[:use_defaults] ? DEFAULT_OPEN_OPTS : CORE_DEFAULT_OPEN_OPTS).dup
|
188
190
|
translate(default_opts)
|
@@ -224,9 +226,9 @@ module RobustExcelOle
|
|
224
226
|
return if @excel && @excel.alive?
|
225
227
|
excel_option = options[:force][:excel] || options[:default][:excel] || :current
|
226
228
|
@excel = if excel_option == :new
|
227
|
-
excel_class.new(:
|
229
|
+
excel_class.new(reuse: false)
|
228
230
|
elsif excel_option == :current
|
229
|
-
excel_class.new(:
|
231
|
+
excel_class.new(reuse: true)
|
230
232
|
elsif excel_option.respond_to?(:to_reo)
|
231
233
|
excel_option.to_reo.excel
|
232
234
|
else
|
@@ -281,14 +283,14 @@ module RobustExcelOle
|
|
281
283
|
|
282
284
|
# connects to an unknown workbook
|
283
285
|
def connect(filename, options)
|
284
|
-
workbooks_number = excel_class.
|
286
|
+
workbooks_number = excel_class.instance_count==0 ? 0 : excel_class.current.Workbooks.Count
|
285
287
|
@ole_workbook = begin
|
286
288
|
WIN32OLE.connect(General.absolute_path(filename))
|
287
289
|
rescue
|
288
290
|
if $!.message =~ /moniker/
|
289
291
|
raise WorkbookConnectingBlockingError, "some workbook is blocking when connecting"
|
290
292
|
else
|
291
|
-
raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook"
|
293
|
+
raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook\n#{$!.message}"
|
292
294
|
end
|
293
295
|
end
|
294
296
|
ole_excel = begin
|
@@ -297,7 +299,7 @@ module RobustExcelOle
|
|
297
299
|
if $!.message =~ /dispid/
|
298
300
|
raise WorkbookConnectingUnsavedError, "workbook is unsaved when connecting"
|
299
301
|
else
|
300
|
-
raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook"
|
302
|
+
raise WorkbookConnectingUnknownError, "unknown error when connecting to a workbook\n#{$!.message}"
|
301
303
|
end
|
302
304
|
end
|
303
305
|
set_was_open options, (ole_excel.Workbooks.Count == workbooks_number)
|
@@ -355,7 +357,7 @@ module RobustExcelOle
|
|
355
357
|
when :close_if_saved
|
356
358
|
if !@ole_workbook.Saved
|
357
359
|
raise WorkbookBlocked, "workbook with the same name in a different path is unsaved: #{blocked_filename.call}" +
|
358
|
-
"\nHint: Use the option :
|
360
|
+
"\nHint: Use the option if_blocked: :save to save the workbook"
|
359
361
|
else
|
360
362
|
manage_forgetting_workbook(filename, options)
|
361
363
|
end
|
@@ -406,7 +408,7 @@ module RobustExcelOle
|
|
406
408
|
end
|
407
409
|
|
408
410
|
def manage_new_excel(filename, options)
|
409
|
-
@excel = excel_class.new(:
|
411
|
+
@excel = excel_class.new(reuse: false)
|
410
412
|
@ole_workbook = nil
|
411
413
|
open_or_create_workbook(filename, options)
|
412
414
|
end
|
@@ -420,50 +422,39 @@ module RobustExcelOle
|
|
420
422
|
raise WorkbookLinked, "read-only mode of this workbook cannot be changed, because it is being used by another workbook"
|
421
423
|
elsif want_change_readonly.nil?
|
422
424
|
raise WorkbookLinked, "workbook is being used by another workbook"
|
423
|
-
else
|
424
|
-
raise UnexpectedREOError, "unknown WIN32OLERuntimeError:\n#{msg.message}"
|
425
425
|
end
|
426
|
-
|
426
|
+
end
|
427
|
+
if msg.message !~ /800A03EC/ || msg.message !~ /0x80020009/ || want_change_readonly==false
|
427
428
|
raise UnexpectedREOError, "unknown WIN32OLERuntimeError:\n#{msg.message}"
|
428
429
|
end
|
429
430
|
end
|
430
431
|
|
431
432
|
def open_or_create_workbook(filename, options)
|
432
433
|
return if @ole_workbook && options[:if_unsaved] != :alert && options[:if_unsaved] != :excel &&
|
433
|
-
|
434
|
+
(options[:read_only].nil? || options[:read_only]==@ole_workbook.ReadOnly )
|
435
|
+
abs_filename = General.absolute_path(filename)
|
436
|
+
workbooks = begin
|
437
|
+
@excel.Workbooks
|
438
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
439
|
+
raise UnexpectedREOError, "cannot access workbooks: #{msg.message} #{msg.backtrace}"
|
440
|
+
end
|
434
441
|
begin
|
435
|
-
|
436
|
-
|
437
|
-
workbooks
|
438
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
439
|
-
raise UnexpectedREOError, "cannot access workbooks: #{msg.message} #{msg.backtrace}"
|
440
|
-
end
|
441
|
-
begin
|
442
|
-
with_workaround_linked_workbooks_excel2007(options) do
|
443
|
-
# temporary workaround until jruby-win32ole implements named parameters (Java::JavaLang::RuntimeException (createVariant() not implemented for class org.jruby.RubyHash)
|
444
|
-
workbooks.Open(abs_filename,
|
445
|
-
updatelinks_vba(options[:update_links]),
|
446
|
-
options[:read_only] )
|
447
|
-
end
|
448
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
449
|
-
# for Excel2007: for option :if_unsaved => :alert and user cancels: this error appears?
|
450
|
-
# if yes: distinguish these events
|
451
|
-
want_change_readonly = !options[:read_only].nil? && (options[:read_only] != @ole_workbook.ReadOnly)
|
452
|
-
explore_workbook_error(msg, want_change_readonly)
|
453
|
-
end
|
454
|
-
begin
|
455
|
-
# workaround for bug in Excel 2010: workbook.Open does not always return the workbook when given file name
|
456
|
-
begin
|
457
|
-
@ole_workbook = workbooks.Item(File.basename(filename))
|
458
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
459
|
-
raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message}"
|
460
|
-
end
|
461
|
-
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
462
|
-
raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message} #{msg.backtrace}"
|
442
|
+
with_workaround_linked_workbooks_excel2007(options) do
|
443
|
+
# temporary workaround until jruby-win32ole implements named parameters (Java::JavaLang::RuntimeException (createVariant() not implemented for class org.jruby.RubyHash)
|
444
|
+
workbooks.Open(abs_filename, updatelinks_vba(options[:update_links]), options[:read_only] )
|
463
445
|
end
|
446
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
447
|
+
# for Excel2007: for option :if_unsaved => :alert and user cancels: this error appears?; distinguish these events
|
448
|
+
want_change_readonly = !options[:read_only].nil? && (options[:read_only] != @ole_workbook.ReadOnly)
|
464
449
|
end
|
465
|
-
|
466
|
-
|
450
|
+
# workaround for bug in Excel 2010: workbook.Open does not always return the workbook when given file name
|
451
|
+
@ole_workbook = begin
|
452
|
+
workbooks.Item(File.basename(filename))
|
453
|
+
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
454
|
+
raise UnexpectedREOError, "WIN32OLERuntimeError: #{msg.message}"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
467
458
|
# translating the option UpdateLinks from REO to VBA
|
468
459
|
# setting UpdateLinks works only if calculation mode is automatic,
|
469
460
|
# parameter 'UpdateLinks' has no effect
|
@@ -502,7 +493,7 @@ module RobustExcelOle
|
|
502
493
|
# @param [String] filename the filename under which the new workbook should be saved
|
503
494
|
# @param [Hash] opts the options as in Workbook::open
|
504
495
|
def self.create(filename, opts = { })
|
505
|
-
open(filename, :
|
496
|
+
open(filename, if_absent: :create)
|
506
497
|
end
|
507
498
|
|
508
499
|
# closes the workbook, if it is alive
|
@@ -517,34 +508,30 @@ module RobustExcelOle
|
|
517
508
|
# :alert or :excel -> gives control to excel
|
518
509
|
# @raise WorkbookNotSaved if the option :if_unsaved is :raise and the workbook is unsaved
|
519
510
|
# @raise OptionInvalid if the options is invalid
|
520
|
-
def close(opts = {:
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
close_workbook
|
529
|
-
when :forget
|
530
|
-
@excel.with_displayalerts(false) { close_workbook }
|
531
|
-
when :keep_open
|
532
|
-
# nothing
|
533
|
-
when :alert, :excel
|
534
|
-
@excel.with_displayalerts(true) { close_workbook }
|
535
|
-
else
|
536
|
-
raise OptionInvalid, ":if_unsaved: invalid option: #{opts[:if_unsaved].inspect}" +
|
537
|
-
"\nHint: Valid values are :raise, :save, :keep_open, :alert, :excel"
|
538
|
-
end
|
539
|
-
else
|
511
|
+
def close(opts = {if_unsaved: :raise})
|
512
|
+
return close_workbook unless (alive? && !@ole_workbook.Saved && writable)
|
513
|
+
case opts[:if_unsaved]
|
514
|
+
when :raise
|
515
|
+
raise WorkbookNotSaved, "workbook is unsaved: #{File.basename(self.stored_filename).inspect}" +
|
516
|
+
"\nHint: Use option :save or :forget to close the workbook with or without saving"
|
517
|
+
when :save
|
518
|
+
save
|
540
519
|
close_workbook
|
520
|
+
when :forget
|
521
|
+
@excel.with_displayalerts(false) { close_workbook }
|
522
|
+
when :keep_open
|
523
|
+
# nothing
|
524
|
+
when :alert, :excel
|
525
|
+
@excel.with_displayalerts(true) { close_workbook }
|
526
|
+
else
|
527
|
+
raise OptionInvalid, ":if_unsaved: invalid option: #{opts[:if_unsaved].inspect}" +
|
528
|
+
"\nHint: Valid values are :raise, :save, :keep_open, :alert, :excel"
|
541
529
|
end
|
542
530
|
end
|
543
531
|
|
544
532
|
private
|
545
533
|
|
546
534
|
def close_workbook
|
547
|
-
#@ole_workbook.Close if alive?
|
548
535
|
if alive?
|
549
536
|
begin
|
550
537
|
@ole_workbook.Close
|
@@ -568,19 +555,19 @@ module RobustExcelOle
|
|
568
555
|
end
|
569
556
|
|
570
557
|
def for_reading(opts = { }, &block)
|
571
|
-
unobtrusively({:
|
558
|
+
unobtrusively({writable: false}.merge(opts), &block)
|
572
559
|
end
|
573
560
|
|
574
561
|
def for_modifying(opts = { }, &block)
|
575
|
-
unobtrusively({:
|
562
|
+
unobtrusively({writable: true}.merge(opts), &block)
|
576
563
|
end
|
577
564
|
|
578
565
|
def self.for_reading(arg, opts = { }, &block)
|
579
|
-
unobtrusively(arg, {:
|
566
|
+
unobtrusively(arg, {writable: false}.merge(opts), &block)
|
580
567
|
end
|
581
568
|
|
582
569
|
def self.for_modifying(arg, opts = { }, &block)
|
583
|
-
unobtrusively(arg, {:
|
570
|
+
unobtrusively(arg, {writable: true}.merge(opts), &block)
|
584
571
|
end
|
585
572
|
|
586
573
|
# allows to read or modify a workbook such that its state remains unchanged
|
@@ -608,29 +595,27 @@ module RobustExcelOle
|
|
608
595
|
|
609
596
|
def self.unobtrusively_opening(file, opts, book_is_alive, &block)
|
610
597
|
process_options(opts)
|
611
|
-
opts = {:
|
612
|
-
raise OptionInvalid,
|
598
|
+
opts = {if_closed: :current, keep_open: false}.merge(opts)
|
599
|
+
raise OptionInvalid, "contradicting options" if opts[:writable] && opts[:read_only]
|
613
600
|
if book_is_alive.nil?
|
614
601
|
prefer_writable = ((!(opts[:read_only]) || opts[:writable] == true) &&
|
615
602
|
!(opts[:read_only].nil? && opts[:writable] == false))
|
616
|
-
known_book = bookstore.fetch(file, :
|
603
|
+
known_book = bookstore.fetch(file, prefer_writable: prefer_writable)
|
617
604
|
end
|
618
605
|
excel_opts = if (book_is_alive==false || (book_is_alive.nil? && (known_book.nil? || !known_book.alive?)))
|
619
|
-
{:
|
606
|
+
{force: {excel: opts[:if_closed]}}
|
620
607
|
else
|
621
|
-
{:
|
608
|
+
{force: {excel: opts[:force][:excel]}, default: {excel: opts[:default][:excel]}}
|
622
609
|
end
|
623
|
-
open_opts = excel_opts.merge({:
|
610
|
+
open_opts = excel_opts.merge({if_unsaved: :accept})
|
624
611
|
begin
|
625
612
|
open_opts[:was_open] = nil
|
626
613
|
book = open(file, open_opts)
|
627
|
-
#book = open(file, :read_only => !opts[:writable]) if !opts[:writable].nil? && !open_opts[:was_open]
|
628
614
|
was_visible = book.visible
|
629
615
|
was_writable = book.writable
|
630
616
|
was_saved = book.saved
|
631
617
|
was_check_compatibility = book.check_compatibility
|
632
618
|
was_calculation = book.excel.properties[:calculation]
|
633
|
-
#opts[:read_only] = !opts[:writable] unless (opts[:writable].nil? || open_opts[:was_open])
|
634
619
|
opts[:read_only] = !opts[:writable] unless (!opts[:read_only].nil? || opts[:writable].nil? || open_opts[:was_open])
|
635
620
|
book.send :apply_options, file, opts
|
636
621
|
yield book
|
@@ -638,10 +623,9 @@ module RobustExcelOle
|
|
638
623
|
if book && book.alive?
|
639
624
|
do_not_write = opts[:read_only] || opts[:writable]==false
|
640
625
|
book.save unless book.saved || do_not_write || !book.writable
|
641
|
-
#if opts[:writable].nil? && opts[:was_open] &&
|
642
626
|
if ((opts[:read_only] && was_writable) || (!opts[:read_only] && !was_writable))
|
643
|
-
book.send :apply_options, file, opts.merge({:
|
644
|
-
:
|
627
|
+
book.send :apply_options, file, opts.merge({read_only: !was_writable,
|
628
|
+
if_unsaved: (opts[:writable]==false ? :forget : :save)})
|
645
629
|
end
|
646
630
|
was_open = open_opts[:was_open]
|
647
631
|
if was_open
|
@@ -661,20 +645,20 @@ module RobustExcelOle
|
|
661
645
|
# @options options
|
662
646
|
def reopen(options = { })
|
663
647
|
book = self.class.open(@stored_filename, options)
|
664
|
-
raise WorkbookREOError(
|
648
|
+
raise WorkbookREOError("cannot reopen workbook\n#{$!.message}") unless book && book.alive?
|
665
649
|
book
|
666
650
|
end
|
667
651
|
|
668
652
|
# simple save of a workbook.
|
669
653
|
# @return [Boolean] true, if successfully saved, nil otherwise
|
670
654
|
def save(opts = { }) # option opts is deprecated #
|
671
|
-
raise ObjectNotAlive,
|
672
|
-
raise WorkbookReadOnly,
|
655
|
+
raise ObjectNotAlive, "workbook is not alive" unless alive?
|
656
|
+
raise WorkbookReadOnly, "Not opened for writing (opened with :read_only option)" if @ole_workbook.ReadOnly
|
673
657
|
begin
|
674
658
|
@ole_workbook.Save
|
675
659
|
rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
|
676
660
|
if msg.message =~ /SaveAs/ && msg.message =~ /Workbook/
|
677
|
-
raise WorkbookNotSaved,
|
661
|
+
raise WorkbookNotSaved, "workbook not saved"
|
678
662
|
else
|
679
663
|
raise UnexpectedREOError, "unknown WIN32OLERuntimeError:\n#{msg.message}"
|
680
664
|
end
|
@@ -700,64 +684,72 @@ module RobustExcelOle
|
|
700
684
|
# otherwise raises an exception
|
701
685
|
# @return [Workbook], the book itself, if successfully saved, raises an exception otherwise
|
702
686
|
def save_as(file, options = { })
|
703
|
-
raise FileNameNotGiven,
|
704
|
-
raise ObjectNotAlive,
|
705
|
-
raise WorkbookReadOnly,
|
687
|
+
raise FileNameNotGiven, "filename is nil" if file.nil?
|
688
|
+
raise ObjectNotAlive, "workbook is not alive" unless alive?
|
689
|
+
raise WorkbookReadOnly, "Not opened for writing (opened with :read_only option)" if @ole_workbook.ReadOnly
|
706
690
|
raise(FileNotFound, "file #{General.absolute_path(file).inspect} is a directory") if File.directory?(file)
|
707
|
-
self.class.process_options(options)
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
raise FileAlreadyExists, "file already exists: #{File.basename(file).inspect}" +
|
728
|
-
"\nHint: Use option :if_exists => :overwrite, if you want to overwrite the file"
|
691
|
+
self.class.process_options(options)
|
692
|
+
begin
|
693
|
+
saveas_manage_if_exists(file, options)
|
694
|
+
saveas_manage_if_blocked(file, options)
|
695
|
+
save_as_workbook(file, options)
|
696
|
+
rescue AlreadyManaged
|
697
|
+
nil
|
698
|
+
end
|
699
|
+
self
|
700
|
+
end
|
701
|
+
|
702
|
+
private
|
703
|
+
|
704
|
+
def saveas_manage_if_exists(file, options)
|
705
|
+
return unless File.exist?(file)
|
706
|
+
case options[:if_exists]
|
707
|
+
when :overwrite
|
708
|
+
if file == self.filename
|
709
|
+
save
|
710
|
+
raise AlreadyManaged
|
729
711
|
else
|
730
|
-
|
731
|
-
|
712
|
+
begin
|
713
|
+
File.delete(file)
|
714
|
+
rescue Errno::EACCES
|
715
|
+
raise WorkbookBeingUsed, "workbook is open and being used in an Excel instance"
|
716
|
+
end
|
732
717
|
end
|
718
|
+
when :alert, :excel
|
719
|
+
@excel.with_displayalerts(true){ save_as_workbook(file, options) }
|
720
|
+
raise AlreadyManaged
|
721
|
+
when :raise
|
722
|
+
raise FileAlreadyExists, "file already exists: #{File.basename(file).inspect}" +
|
723
|
+
"\nHint: Use option if_exists: :overwrite, if you want to overwrite the file"
|
724
|
+
else
|
725
|
+
raise OptionInvalid, ":if_exists: invalid option: #{options[:if_exists].inspect}" +
|
726
|
+
"\nHint: Valid values are :raise, :overwrite, :alert, :excel"
|
733
727
|
end
|
728
|
+
end
|
729
|
+
|
730
|
+
def saveas_manage_if_blocked(file, options)
|
734
731
|
other_workbook = @excel.Workbooks.Item(File.basename(file)) rescue nil
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
end
|
750
|
-
else
|
751
|
-
raise OptionInvalid, ":if_blocked: invalid option: #{options[:if_obstructed].inspect}" +
|
752
|
-
"\nHint: Valid values are :raise, :forget, :save, :close_if_saved"
|
732
|
+
return unless other_workbook && self.filename != other_workbook.Fullname.tr('\\','/')
|
733
|
+
case options[:if_obstructed]
|
734
|
+
when :raise
|
735
|
+
raise WorkbookBlocked, "blocked by another workbook: #{other_workbook.Fullname.tr('\\','/')}" +
|
736
|
+
"\nHint: Use the option :if_blocked with values :forget or :save to
|
737
|
+
close or save and close the blocking workbook"
|
738
|
+
when :forget
|
739
|
+
# nothing
|
740
|
+
when :save
|
741
|
+
other_workbook.Save
|
742
|
+
when :close_if_saved
|
743
|
+
unless other_workbook.Saved
|
744
|
+
raise WorkbookBlocked, "blocking workbook is unsaved: #{File.basename(file).inspect}" +
|
745
|
+
"\nHint: Use option if_blocked: :save to save the blocking workbooks"
|
753
746
|
end
|
754
|
-
|
747
|
+
else
|
748
|
+
raise OptionInvalid, "if_blocked: invalid option: #{options[:if_obstructed].inspect}" +
|
749
|
+
"\nHint: Valid values are :raise, :forget, :save, :close_if_saved"
|
755
750
|
end
|
756
|
-
|
757
|
-
|
758
|
-
end
|
759
|
-
|
760
|
-
private
|
751
|
+
other_workbook.Close
|
752
|
+
end
|
761
753
|
|
762
754
|
def save_as_workbook(file, options)
|
763
755
|
dirname, basename = File.split(file)
|
@@ -778,6 +770,9 @@ module RobustExcelOle
|
|
778
770
|
end
|
779
771
|
end
|
780
772
|
|
773
|
+
class AlreadyManaged < Exception
|
774
|
+
end
|
775
|
+
|
781
776
|
def store_myself
|
782
777
|
bookstore.store(self)
|
783
778
|
@stored_filename = filename
|
@@ -787,7 +782,7 @@ module RobustExcelOle
|
|
787
782
|
|
788
783
|
# closes a given file if it is open
|
789
784
|
# @options opts [Symbol] :if_unsaved
|
790
|
-
def self.close(file, opts = {:
|
785
|
+
def self.close(file, opts = {if_unsaved: :raise})
|
791
786
|
book = begin
|
792
787
|
bookstore.fetch(file)
|
793
788
|
rescue
|
@@ -825,21 +820,20 @@ module RobustExcelOle
|
|
825
820
|
@ole_workbook.Worksheets.Count
|
826
821
|
end
|
827
822
|
|
823
|
+
# @return [Enumerator] traversing all worksheet objects
|
828
824
|
def each
|
829
|
-
|
830
|
-
|
825
|
+
if block_given?
|
826
|
+
@ole_workbook.Worksheets.lazy.each do |ole_worksheet|
|
827
|
+
yield worksheet_class.new(ole_worksheet)
|
828
|
+
end
|
829
|
+
else
|
830
|
+
to_enum(:each).lazy
|
831
831
|
end
|
832
832
|
end
|
833
833
|
|
834
|
-
def worksheets
|
835
|
-
result = []
|
836
|
-
each { |worksheet| result << worksheet }
|
837
|
-
result
|
838
|
-
end
|
839
|
-
|
840
834
|
def each_with_index(offset = 0)
|
841
835
|
i = offset
|
842
|
-
@ole_workbook.Worksheets.each do |sheet|
|
836
|
+
@ole_workbook.Worksheets.lazy.each do |sheet|
|
843
837
|
yield worksheet_class.new(sheet), i
|
844
838
|
i += 1
|
845
839
|
end
|
@@ -880,7 +874,7 @@ module RobustExcelOle
|
|
880
874
|
end
|
881
875
|
end
|
882
876
|
rescue # WIN32OLERuntimeError, NameNotFound, Java::OrgRacobCom::ComFailException
|
883
|
-
raise WorksheetREOError, "could not add given worksheet #{sheet.inspect}"
|
877
|
+
raise WorksheetREOError, "could not add given worksheet #{sheet.inspect}\n#{$!.message}"
|
884
878
|
end
|
885
879
|
new_sheet = worksheet_class.new(ole_workbook.Activesheet)
|
886
880
|
new_sheet.name = new_sheet_name if new_sheet_name
|
@@ -924,7 +918,7 @@ module RobustExcelOle
|
|
924
918
|
def range(name_or_worksheet, name_or_address = :__not_provided, address2 = :__not_provided)
|
925
919
|
if name_or_worksheet.respond_to?(:gsub)
|
926
920
|
name = name_or_worksheet
|
927
|
-
RobustExcelOle::Range.new(
|
921
|
+
RobustExcelOle::Range.new(get_name_object(name).RefersToRange)
|
928
922
|
else
|
929
923
|
begin
|
930
924
|
worksheet = name_or_worksheet.to_reo
|
@@ -953,7 +947,7 @@ module RobustExcelOle
|
|
953
947
|
# @param [Hash] opts
|
954
948
|
def for_this_workbook(opts)
|
955
949
|
return unless alive?
|
956
|
-
self.class.process_options(opts, :
|
950
|
+
self.class.process_options(opts, use_defaults: false)
|
957
951
|
self.send :apply_options, @stored_filename, opts
|
958
952
|
end
|
959
953
|
|
@@ -1058,7 +1052,8 @@ module RobustExcelOle
|
|
1058
1052
|
|
1059
1053
|
# @private
|
1060
1054
|
def inspect
|
1061
|
-
|
1055
|
+
#{}"#<Workbook: #{("not alive " unless alive?)} #{(File.basename(self.filename) if alive?)} #{@excel}>"
|
1056
|
+
"#<Workbook: #{(alive? ? File.basename(self.filename) : "not alive")} #{@excel} >"
|
1062
1057
|
end
|
1063
1058
|
|
1064
1059
|
using ParentRefinement
|
@@ -1100,23 +1095,20 @@ module RobustExcelOle
|
|
1100
1095
|
private
|
1101
1096
|
|
1102
1097
|
def method_missing(name, *args)
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
end
|
1111
|
-
else
|
1112
|
-
begin
|
1113
|
-
@ole_workbook.send(name, *args)
|
1114
|
-
rescue NoMethodError
|
1115
|
-
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
1116
|
-
end
|
1098
|
+
super unless name.to_s[0,1] =~ /[A-Z]/
|
1099
|
+
raise ObjectNotAlive, 'method missing: workbook not alive' unless alive?
|
1100
|
+
if ::ERRORMESSAGE_JRUBY_BUG
|
1101
|
+
begin
|
1102
|
+
@ole_workbook.send(name, *args)
|
1103
|
+
rescue Java::OrgRacobCom::ComFailException
|
1104
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
1117
1105
|
end
|
1118
1106
|
else
|
1119
|
-
|
1107
|
+
begin
|
1108
|
+
@ole_workbook.send(name, *args)
|
1109
|
+
rescue NoMethodError
|
1110
|
+
raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
|
1111
|
+
end
|
1120
1112
|
end
|
1121
1113
|
end
|
1122
1114
|
|