robust_excel_ole 1.8 → 1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Changelog +8 -0
- data/README.rdoc +17 -3
- data/docs/README_excel.rdoc +23 -0
- data/docs/README_ranges.rdoc +73 -5
- data/examples/modifying_sheets/example_add_names.rb +57 -0
- data/examples/modifying_sheets/example_adding_sheets.rb +1 -1
- data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
- data/examples/open_save_close/example_if_unsaved_forget.rb +1 -1
- data/examples/open_save_close/example_reuse.rb +1 -1
- data/lib/robust_excel_ole.rb +2 -0
- data/lib/robust_excel_ole/address.rb +114 -0
- data/lib/robust_excel_ole/excel.rb +81 -53
- data/lib/robust_excel_ole/general.rb +37 -3
- data/lib/robust_excel_ole/range.rb +20 -20
- data/lib/robust_excel_ole/range_owners.rb +236 -0
- data/lib/robust_excel_ole/reo_common.rb +18 -276
- data/lib/robust_excel_ole/version.rb +1 -1
- data/lib/robust_excel_ole/workbook.rb +177 -91
- data/lib/robust_excel_ole/worksheet.rb +12 -4
- data/reo.bat +1 -1
- data/spec/address_spec.rb +174 -0
- data/spec/data/another_workbook.xls +0 -0
- data/spec/data/different_workbook.xls +0 -0
- data/spec/data/workbook.xls +0 -0
- data/spec/excel_spec.rb +168 -29
- data/spec/reo_common_spec.rb +0 -103
- data/spec/workbook_spec.rb +12 -2
- data/spec/workbook_specs/workbook_close_spec.rb +2 -1
- data/spec/workbook_specs/workbook_misc_spec.rb +41 -7
- data/spec/workbook_specs/workbook_open_spec.rb +8 -3
- data/spec/workbook_specs/workbook_save_spec.rb +5 -3
- data/spec/worksheet_spec.rb +83 -10
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3cf45dfa8d36893f06b8196d99990c94b76056fdfd8b98e2a1cff18f11dedec5
|
4
|
+
data.tar.gz: 2fb8ee7deb7bb700c9a7557340933b0a60a6ee38a8eaff9ed2c2f4b4669cadc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b8c23bf581b8ec11b9e510bf0bf805e2309a089a307c01bdcaf4aead8dc1aa31becd972e94beedb5e7ac82183ac4f84bd0c5ea6883a4a15baa2d2f95fd13dba
|
7
|
+
data.tar.gz: 6958f89f6a6204f359a2f16469ca602a33b1572588b31944a073ffbc48936a9e9412edd4b742bb57ecff4e3b96756e99258924f60d5d2b7aaf8fefabafc0c697
|
data/Changelog
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## [1.9]
|
5
|
+
|
6
|
+
### Added
|
7
|
+
- Workbook#create
|
8
|
+
- Excel#workbooks, each_workbook, each_workbook_with_index
|
9
|
+
- Address#int_range,#r1c1,#a1
|
10
|
+
- ReoCommon::RangeOwners#add_name: allowing infinite ranges
|
11
|
+
|
4
12
|
## [1.6]
|
5
13
|
|
6
14
|
### Added
|
data/README.rdoc
CHANGED
@@ -28,13 +28,17 @@ RobustExcelOle can be used either for scripts
|
|
28
28
|
require 'robust_excel_ole'
|
29
29
|
include RobustExcelOle
|
30
30
|
|
31
|
-
or as a console
|
31
|
+
or as a console. If you want to use RobustExcelOle as a console, you go (using 'cd') into the gem path that you find with help of
|
32
|
+
|
33
|
+
gem env
|
34
|
+
|
35
|
+
and go into the path of the gem 'robust_excel_ole'. There you start the console via the command
|
32
36
|
|
33
37
|
reo
|
34
38
|
|
35
39
|
The call of the console will include RobustExcelOle for you.
|
36
40
|
|
37
|
-
The following examples can be used for both scripts and console. If you
|
41
|
+
The following examples can be used for both scripts and console. If you have started the console in the gem path, you can just put these examples.
|
38
42
|
|
39
43
|
== Description
|
40
44
|
|
@@ -100,7 +104,11 @@ RobustExcelOle allows unobtrusively reading and modifying workbooks, i.e. access
|
|
100
104
|
# do something
|
101
105
|
end
|
102
106
|
|
103
|
-
|
107
|
+
You can also create a new, empty workbook.
|
108
|
+
|
109
|
+
Workbook.create('spec/data/new_workbook.xls', :visible => true)
|
110
|
+
|
111
|
+
=== More features when opening, modifying, creating saving and closing workbooks
|
104
112
|
|
105
113
|
We can open the workbook using a block, similar to, e.g., +File.open+.
|
106
114
|
|
@@ -174,6 +182,12 @@ For hard terminating all Excel processes we can use
|
|
174
182
|
|
175
183
|
For more details about creating Excel instances see README_excel[https://github.com/Thomas008/robust_excel_ole/blob/master/README/README_excel_rdoc]
|
176
184
|
|
185
|
+
=== Generating a new workbook
|
186
|
+
|
187
|
+
You can creata a new, empty workbook by
|
188
|
+
|
189
|
+
Excel.generate_workbook('spec/data/new_workbook.xls')
|
190
|
+
|
177
191
|
=== Opening workbooks in several Excel instances
|
178
192
|
|
179
193
|
RobustExcelOle enables opening and processing workbooks in several Excel instances. Using more than one Excel process allows, e.g., running a script that operates in one Excel instance, while a user (or another script) modifies workbooks in another Excel instance.
|
data/docs/README_excel.rdoc
CHANGED
@@ -101,6 +101,29 @@ You can set options for all workbooks of an Excel instance.
|
|
101
101
|
|
102
102
|
excel.for_all_workooks(:visible => true, :read_only => true)
|
103
103
|
|
104
|
+
|
105
|
+
=== Showing and traversing through all workbooks
|
106
|
+
|
107
|
+
You can yield an array of all Workbook objects of an Excel instance.
|
108
|
+
|
109
|
+
excel.workbooks
|
110
|
+
|
111
|
+
You can access all Workbook objects by using the methods Excel#each_workbook and Excel#each_workbook_with_index. Here are some examples:
|
112
|
+
|
113
|
+
excel.each_workook {|w| puts w.filename}
|
114
|
+
|
115
|
+
and
|
116
|
+
|
117
|
+
excel.each_workbook.with_index {|w,i| puts w,i }
|
118
|
+
|
119
|
+
With help of these methods you can also supply options, e.g. without a block,
|
120
|
+
|
121
|
+
excel.each_workbook(:visible => true)
|
122
|
+
|
123
|
+
or, with a block,
|
124
|
+
|
125
|
+
excel.each_workbook(:visible => true) {|w| puts w}
|
126
|
+
|
104
127
|
=== Bringing an Excel instance to the foreground
|
105
128
|
|
106
129
|
excel1.focus
|
data/docs/README_ranges.rdoc
CHANGED
@@ -142,15 +142,21 @@ For example, you can access a range consisting of one cell by providing the row
|
|
142
142
|
|
143
143
|
range = sheet.range([1,1])
|
144
144
|
|
145
|
-
Using the A1-format you write
|
145
|
+
Using the A1-format and R1C1-format you write
|
146
146
|
|
147
147
|
range = sheet.range("A1")
|
148
148
|
|
149
|
+
and
|
150
|
+
|
151
|
+
range = sheet.range("Z1S1")
|
152
|
+
|
153
|
+
respectively.
|
154
|
+
|
149
155
|
You can access a rectangular range by providing the row and column of the top left cell and the row and column of the bottum right cell.
|
150
156
|
|
151
157
|
range = sheet.range([1..3,1..4])
|
152
158
|
|
153
|
-
or using the
|
159
|
+
or using the A1-format.
|
154
160
|
|
155
161
|
range = sheet.range([1..3,"A".."D"])
|
156
162
|
|
@@ -158,6 +164,28 @@ or
|
|
158
164
|
|
159
165
|
range = sheet.range("A1:D3")
|
160
166
|
|
167
|
+
or using the R1C1-format
|
168
|
+
|
169
|
+
range = sheet.range("Z1S1:Z3S4")
|
170
|
+
|
171
|
+
Infinite ranges are defined, e.g., by setting the rows or columns to +nil+
|
172
|
+
|
173
|
+
range = sheet.range([1..3,nil])
|
174
|
+
range = sheet.range([nil,"A".."B"])
|
175
|
+
|
176
|
+
Using the A1-format you write
|
177
|
+
|
178
|
+
range = sheet.range("1:3")
|
179
|
+
range = sheet.range("A:B")
|
180
|
+
|
181
|
+
You can also apply relative references by using brackets, e.g.
|
182
|
+
|
183
|
+
range = sheet.range("Z[1]S1:Z3S[4]")
|
184
|
+
|
185
|
+
or
|
186
|
+
|
187
|
+
range = sheet.range([[1]..3,2..[4]])
|
188
|
+
|
161
189
|
You get the values of the range as flat array with help of
|
162
190
|
|
163
191
|
range.values
|
@@ -194,13 +222,53 @@ Moreover, you can state, whether you want to copy the values only, and whether y
|
|
194
222
|
|
195
223
|
Note that when you don't copy the values only but all formating as well, and you either copy into another Excel instance or transpose the range, the clipboard is being used.
|
196
224
|
|
197
|
-
=== Naming a
|
225
|
+
=== Naming a range
|
226
|
+
|
227
|
+
You can (re-) define a name referring to a range by stating its name, and the address. The address is given by integer-range-format, r1c1-format or a1-format. For example, you can define a name for a rectangular range by
|
198
228
|
|
199
|
-
|
229
|
+
book.add_name("name",[1..2,3..5])
|
230
|
+
|
231
|
+
or
|
232
|
+
|
233
|
+
book.add_name("name","Z1S3:Z2S5")
|
234
|
+
|
235
|
+
or
|
236
|
+
|
237
|
+
book.add_name("name", "C1:E2")
|
238
|
+
|
239
|
+
Similarly you can define a name referring to a cell
|
200
240
|
|
201
241
|
book.add_name("name",[1,1])
|
202
242
|
|
203
|
-
|
243
|
+
or
|
244
|
+
|
245
|
+
book.add_name("name","Z1S1")
|
246
|
+
|
247
|
+
or
|
248
|
+
|
249
|
+
book.add_name("name","A1")
|
250
|
+
|
251
|
+
and infinite ranges
|
252
|
+
|
253
|
+
book.add_name("name",[1..2,nil])
|
254
|
+
|
255
|
+
or
|
256
|
+
|
257
|
+
book.add_name("name", "Z1:Z2")
|
258
|
+
|
259
|
+
or
|
260
|
+
|
261
|
+
book.add_name("name","1:2")
|
262
|
+
|
263
|
+
Furthermore, you can define a name using relative references with the r1c1-format.
|
264
|
+
|
265
|
+
book.add_name("name", "Z1S[1]:Z[2]S4")
|
266
|
+
|
267
|
+
or
|
268
|
+
|
269
|
+
book.add_name("name", [1..[2],[1]..4])
|
270
|
+
|
271
|
+
You can do the same for an worksheet or Excel object.
|
204
272
|
|
205
273
|
=== Reading and writing the contents of a named range in a workbook.
|
206
274
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# example_naming.rb:
|
2
|
+
# each cell is named with the name equaling its value unless it is empty or not a string
|
3
|
+
# the contents of each cell is copied
|
4
|
+
# the new workbook's name is extended by the suffix "_named"
|
5
|
+
|
6
|
+
require File.expand_path('../../lib/robust_excel_ole', File.dirname(__FILE__))
|
7
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), '../../spec/helpers/create_temporary_dir')
|
8
|
+
require "fileutils"
|
9
|
+
|
10
|
+
include RobustExcelOle
|
11
|
+
|
12
|
+
begin
|
13
|
+
@id2exl = ["house", "tree", "cat", "mouse", "elephant", "yes", "no"]
|
14
|
+
column_ids = [2,4,6]
|
15
|
+
dir = File.expand_path('../../spec/data', File.dirname(__FILE__))
|
16
|
+
workbook_name = 'workbook.xls'
|
17
|
+
filename = dir + "/" + workbook_name
|
18
|
+
puts "filename: #{filename}"
|
19
|
+
|
20
|
+
Excel.close_all if_unsaved: :forget
|
21
|
+
book = Workbook.new filename, if_absent: :create, visible: true, if_unsaved: :accept
|
22
|
+
sheet = book.sheet(1)
|
23
|
+
puts "book: #{book}"
|
24
|
+
puts "sheet: #{sheet}"
|
25
|
+
|
26
|
+
# book.Names.Add("µ", RefersToR1C1Local:"=Z")
|
27
|
+
# book.Names.Add("µ_1", RefersToR1C1Local:"=Z(-1)")
|
28
|
+
|
29
|
+
def define_columns sheet, columns_ids
|
30
|
+
puts "define_columns:"
|
31
|
+
first_column = sheet.range("A")
|
32
|
+
puts "first_column: #{first_column}"
|
33
|
+
|
34
|
+
columns_ids.each_with_index do |id,idx|
|
35
|
+
puts "id: #{id}"
|
36
|
+
puts "idx: #{idx}"
|
37
|
+
|
38
|
+
nam = @id2exl[id]
|
39
|
+
puts "nam: #{nam}"
|
40
|
+
colnr = idx+1
|
41
|
+
puts "colnr: #{colnr}"
|
42
|
+
sheet[1,colnr] = nam
|
43
|
+
puts "sheet[1,colnr]: #{sheet[1,colnr].Value}"
|
44
|
+
sheet.add_name(nam,[nil,colnr])
|
45
|
+
puts "sheet.Range().Address: #{sheet.Range(nam).Address}"
|
46
|
+
#sheet.add_name(nam,"A")
|
47
|
+
# blatt.Names.Add(Name: nam, RefersTo: "="+erste_spalte.Offset(0,idx).Address)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
define_columns sheet, column_ids
|
52
|
+
|
53
|
+
|
54
|
+
# book.save
|
55
|
+
|
56
|
+
end
|
57
|
+
|
@@ -57,7 +57,7 @@ begin
|
|
57
57
|
begin
|
58
58
|
@book.add_sheet(sheet, :as => 'second_sheet_copy')
|
59
59
|
rescue NameAlreadyExists => msg
|
60
|
-
puts "error: add_sheet: #{msg.message}"
|
60
|
+
puts "results in an error: add_sheet: #{msg.message}"
|
61
61
|
end
|
62
62
|
|
63
63
|
@book.close(:if_unsaved => :forget) # close the book without saving it
|
@@ -13,7 +13,7 @@ begin
|
|
13
13
|
file_name2 = dir + 'different_workbook.xls'
|
14
14
|
file_name3 = dir + 'different_workbook.xls'
|
15
15
|
file_name4 = dir + 'book_with_blank.xls'
|
16
|
-
book1 = Workbook.open(file_name1)
|
16
|
+
book1 = Workbook.open(file_name1) # open a book in a new Excel instance since no Excel is open
|
17
17
|
book1.excel.visible = true # make current Excel visible
|
18
18
|
sleep 2
|
19
19
|
book2 = Workbook.open(file_name2) # open a new book in the same Excel instance
|
data/lib/robust_excel_ole.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'win32ole'
|
2
2
|
require File.join(File.dirname(__FILE__), 'robust_excel_ole/reo_common')
|
3
|
+
require File.join(File.dirname(__FILE__), 'robust_excel_ole/range_owners')
|
4
|
+
require File.join(File.dirname(__FILE__), 'robust_excel_ole/address')
|
3
5
|
require File.join(File.dirname(__FILE__), 'robust_excel_ole/general')
|
4
6
|
require File.join(File.dirname(__FILE__), 'robust_excel_ole/excel')
|
5
7
|
require File.join(File.dirname(__FILE__), 'robust_excel_ole/bookstore')
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module RobustExcelOle
|
4
|
+
|
5
|
+
class Address < REOCommon
|
6
|
+
|
7
|
+
def self.new(r1c1_letters)
|
8
|
+
@@row_letter = r1c1_letters[0..0]
|
9
|
+
@@col_letter = r1c1_letters[1..1]
|
10
|
+
end
|
11
|
+
|
12
|
+
# address formats taht are valid:
|
13
|
+
# r1c1-format: e.g. "Z3S1", "Z3S1:Z5S2", "Z[3]S1", "Z3S[-1]:Z[5]S1", "Z[3]", "S[-2]"
|
14
|
+
# infinite ranges are not possible, e.g. "Z3:Z5", "S2:S5", "Z2", "S3", "Z[2]"
|
15
|
+
# int_range: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
|
16
|
+
# [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
|
17
|
+
# a1-format: e.g. "A3", "A3:B5", "A:B", "3:5", "A", "3"
|
18
|
+
|
19
|
+
def self.r1c1(address)
|
20
|
+
transform_address(address,:r1c1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.a1(address)
|
24
|
+
transform_address(address,:a1)
|
25
|
+
end
|
26
|
+
|
27
|
+
# valid address formats: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
|
28
|
+
# [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
|
29
|
+
def self.int_range(address)
|
30
|
+
transform_address(address,:int_range)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.transform_address(address, format)
|
36
|
+
address = address.is_a?(Array) ? address : [address]
|
37
|
+
raise AddressInvalid, "address #{address.inspect} has more than two components" if address.size > 2
|
38
|
+
begin
|
39
|
+
if address.size == 1
|
40
|
+
comp1, comp2 = address[0].split(':')
|
41
|
+
a1_expr = /^(([A-Z]+[0-9]+)|([A-Z]+$)|([0-9]+))$/
|
42
|
+
is_a1 = comp1 =~ a1_expr && (comp2.nil? || comp2 =~ a1_expr)
|
43
|
+
r1c1_expr = /^(([A-Z]\[?-?[0-9]+\]?[A-Z]\[?-?[0-9]+\]?)|([A-Z]\[?-?[0-9]+\]?)|([A-Z]\[?-?[0-9]+\]?))$/
|
44
|
+
is_r1c1 = comp1 =~ r1c1_expr && (comp2.nil? || comp2 =~ r1c1_expr) && (not is_a1)
|
45
|
+
raise AddressInvalid, "address #{address.inspect} not in A1- or r1c1-format" unless (is_a1 || is_r1c1)
|
46
|
+
return address[0].gsub('[','(').gsub(']',')') if (is_a1 && format==:a1) || (is_r1c1 && format==:r1c1)
|
47
|
+
given_format = (is_a1) ? :a1 : :r1c1
|
48
|
+
row_comp1, col_comp1 = analyze(comp1,given_format)
|
49
|
+
row_comp2, col_comp2 = analyze(comp2,given_format) unless comp2.nil?
|
50
|
+
address_comp1 = comp2 && (not row_comp1.nil?) ? (row_comp1 .. row_comp2) : row_comp1
|
51
|
+
address_comp2 = comp2 && (not col_comp1.nil?) ? (col_comp1 .. col_comp2) : col_comp1
|
52
|
+
else
|
53
|
+
address_comp1, address_comp2 = address
|
54
|
+
end
|
55
|
+
address_comp1 = address_comp1..address_comp1 if (address_comp1.is_a?(Integer) || address_comp1.is_a?(String) || address_comp1.is_a?(Array))
|
56
|
+
address_comp2 = address_comp2..address_comp2 if (address_comp2.is_a?(Integer) || address_comp2.is_a?(String) || address_comp2.is_a?(Array))
|
57
|
+
#raise unless address_comp1.nil? || address_comp1.begin.to_i!=0 || address_comp1.begin.empty?
|
58
|
+
rows = unless address_comp1.nil? || address_comp1.begin == 0 # || address_comp1.begin.to_i==0
|
59
|
+
address_comp1.begin..address_comp1.end
|
60
|
+
end
|
61
|
+
columns = unless address_comp2.nil?
|
62
|
+
if address_comp2.begin.is_a?(String) #address_comp2.begin.to_i == 0
|
63
|
+
col_range = str2num(address_comp2.begin)..str2num(address_comp2.end)
|
64
|
+
col_range==(0..0) ? nil : col_range
|
65
|
+
else
|
66
|
+
address_comp2.begin..address_comp2.end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue
|
70
|
+
raise AddressInvalid, "address (#{address.inspect}) format not correct"
|
71
|
+
end
|
72
|
+
if format==:r1c1
|
73
|
+
r1c1_string(@@row_letter,rows,:min) + r1c1_string(@@col_letter,columns,:min) + ":" +
|
74
|
+
r1c1_string(@@row_letter,rows,:max) + r1c1_string(@@col_letter,columns,:max)
|
75
|
+
elsif format==:int_range
|
76
|
+
[rows,columns]
|
77
|
+
else
|
78
|
+
raise NotImplementedREOError, "not implemented"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @private
|
83
|
+
def self.r1c1_string(letter,int_range,type)
|
84
|
+
return "" if int_range.nil? || int_range.begin.nil?
|
85
|
+
parameter = type == :min ? int_range.begin : int_range.end
|
86
|
+
is_relative = parameter.is_a?(Array)
|
87
|
+
parameter = parameter.first if is_relative
|
88
|
+
letter + (is_relative ? "(" : "") + parameter.to_s + (is_relative ? ")" : "")
|
89
|
+
end
|
90
|
+
|
91
|
+
# @private
|
92
|
+
def self.analyze(comp,format)
|
93
|
+
row_comp, col_comp = if format==:a1
|
94
|
+
[comp.gsub(/[A-Z]/,''), comp.gsub(/[0-9]/,'')]
|
95
|
+
else
|
96
|
+
a,b = comp.split(@@row_letter)
|
97
|
+
c,d = b.split(@@col_letter)
|
98
|
+
b.nil? ? ["",b] : (d.nil? ? [c,""] : [c,d])
|
99
|
+
end
|
100
|
+
def self.s2n(s)
|
101
|
+
s!="" ? (s[0] == "[" ? [s.gsub(/\[|\]/,'').to_i] : (s.to_i!=0 ? s.to_i : s)) : nil
|
102
|
+
end
|
103
|
+
[s2n(row_comp), s2n(col_comp)]
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# @private
|
108
|
+
def self.str2num(str)
|
109
|
+
str.tr("A-Z","0-9A-P").to_i(26) + (26**str.size-1)/25
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -135,7 +135,7 @@ module RobustExcelOle
|
|
135
135
|
self
|
136
136
|
end
|
137
137
|
|
138
|
-
|
138
|
+
private
|
139
139
|
|
140
140
|
# returns a Win32OLE object that represents a Excel instance to which Excel connects
|
141
141
|
# connects to the first opened Excel instance
|
@@ -162,23 +162,32 @@ module RobustExcelOle
|
|
162
162
|
result
|
163
163
|
end
|
164
164
|
|
165
|
-
public
|
166
|
-
|
167
165
|
# retain the saved status of all workbooks
|
166
|
+
# @private
|
168
167
|
def retain_saved_workbooks
|
169
|
-
|
170
|
-
@ole_excel.Workbooks.each { |w| saved << w.Saved }
|
168
|
+
saved_stati = @ole_excel.Workbooks.map { |w| w.Saved }
|
171
169
|
begin
|
172
170
|
yield self
|
173
171
|
ensure
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
172
|
+
@ole_excel.Workbooks.zip(saved_stati) { |w,s| w.Saved = s }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# @private
|
177
|
+
def ole_workbooks
|
178
|
+
ole_workbooks = begin
|
179
|
+
@ole_excel.Workbooks
|
180
|
+
rescue WIN32OLERuntimeError => msg
|
181
|
+
if msg.message =~ /failed to get Dispatch Interface/
|
182
|
+
raise ExcelDamaged, 'Excel instance not alive or damaged'
|
183
|
+
else
|
184
|
+
raise ExcelREOError, 'workbooks could not be determined'
|
178
185
|
end
|
179
186
|
end
|
180
187
|
end
|
181
188
|
|
189
|
+
public
|
190
|
+
|
182
191
|
def self.contains_unsaved_workbooks?
|
183
192
|
!Excel.current.unsaved_workbooks.empty?
|
184
193
|
end
|
@@ -198,8 +207,8 @@ module RobustExcelOle
|
|
198
207
|
# @option options [Symbol] :if_unsaved :raise, :save, :forget, :alert, Proc
|
199
208
|
# :if_unsaved if unsaved workbooks are open in an Excel instance
|
200
209
|
# :raise (default) -> raises an exception
|
201
|
-
# :save -> saves the workbooks before closing
|
202
210
|
# :forget -> closes the Excel instance without saving the workbooks
|
211
|
+
# :save -> saves the workbooks before closing
|
203
212
|
# :alert -> let Excel do it
|
204
213
|
def close_workbooks(options = { :if_unsaved => :raise })
|
205
214
|
return unless alive?
|
@@ -210,7 +219,9 @@ module RobustExcelOle
|
|
210
219
|
when Proc
|
211
220
|
options[:if_unsaved].call(self, unsaved_workbooks)
|
212
221
|
when :raise
|
213
|
-
raise UnsavedWorkbooks,
|
222
|
+
raise UnsavedWorkbooks, "Excel contains unsaved workbooks" +
|
223
|
+
"\nHint: Use option :if_unsaved with values :forget and :save to close the
|
224
|
+
Excel instance without or with saving the unsaved workbooks before, respectively"
|
214
225
|
when :alert
|
215
226
|
# nothing
|
216
227
|
when :forget
|
@@ -218,7 +229,8 @@ module RobustExcelOle
|
|
218
229
|
when :save
|
219
230
|
unsaved_workbooks.each { |m| m.Save }
|
220
231
|
else
|
221
|
-
raise OptionInvalid, ":if_unsaved: invalid option: #{options[:if_unsaved].inspect}"
|
232
|
+
raise OptionInvalid, ":if_unsaved: invalid option: #{options[:if_unsaved].inspect}" +
|
233
|
+
"\nHint: Valid values are :raise, :forget, :save and :alert"
|
222
234
|
end
|
223
235
|
end
|
224
236
|
begin
|
@@ -501,27 +513,9 @@ module RobustExcelOle
|
|
501
513
|
self.Workbooks.each { |w| trace "#{w.Name} #{w}" }
|
502
514
|
end
|
503
515
|
|
504
|
-
# generates, saves, and closes empty workbook
|
505
516
|
# @private
|
506
|
-
def generate_workbook file_name
|
507
|
-
|
508
|
-
|
509
|
-
self.Workbooks.Add
|
510
|
-
empty_workbook = self.Workbooks.Item(self.Workbooks.Count)
|
511
|
-
filename = General.absolute_path(file_name).tr('/','\\')
|
512
|
-
unless File.exist?(filename)
|
513
|
-
begin
|
514
|
-
empty_workbook.SaveAs(filename)
|
515
|
-
rescue WIN32OLERuntimeError => msg
|
516
|
-
# if msg.message =~ /SaveAs/ and msg.message =~ /Workbook/ then
|
517
|
-
raise FileNotFound, "could not save workbook with filename #{file_name.inspect}"
|
518
|
-
# else
|
519
|
-
# # todo some time: find out when this occurs :
|
520
|
-
# raise UnexpectedREOError, "unknown WIN32OLERuntimeError with filename #{file_name.inspect}: \n#{msg.message}"
|
521
|
-
# end
|
522
|
-
end
|
523
|
-
end
|
524
|
-
empty_workbook
|
517
|
+
def generate_workbook file_name # :deprecated: #
|
518
|
+
workbook_class.open(file_name, :if_absent => :create, :force => {:excel => self})
|
525
519
|
end
|
526
520
|
|
527
521
|
# sets DisplayAlerts in a block
|
@@ -559,16 +553,36 @@ module RobustExcelOle
|
|
559
553
|
@calculation = calculation_mode
|
560
554
|
calc_mode_changable = @ole_excel.Workbooks.Count > 0 && @ole_excel.Calculation.is_a?(Integer)
|
561
555
|
if calc_mode_changable
|
562
|
-
|
556
|
+
retain_saved_workbooks do
|
557
|
+
begin
|
558
|
+
best_wb_to_make_visible = @ole_excel.Workbooks.sort_by {|wb|
|
559
|
+
score =
|
560
|
+
(wb.Saved ? 0 : 40) + # an unsaved workbooks is most likely the main workbook
|
561
|
+
(wb.ReadOnly ? 0 : 20) + # the main wb is usually writable
|
562
|
+
case wb.Name.split(".").last.downcase
|
563
|
+
when "xlsm" then 10 # the main workbook is more likely to have macros
|
564
|
+
when "xls" then 8
|
565
|
+
when "xlsx" then 4
|
566
|
+
when "xlam" then -2 # libraries are not normally the main workbook
|
567
|
+
else 0
|
568
|
+
end
|
569
|
+
score
|
570
|
+
}.last
|
571
|
+
best_wb_to_make_visible.Windows(1).Visible = true
|
572
|
+
rescue => e
|
573
|
+
trace "error setting calculation=#{calculation_mode} msg: " + e.message
|
574
|
+
trace e.backtrace
|
575
|
+
# continue on errors here, failing would usually disrupt too much
|
576
|
+
end
|
563
577
|
saved = []
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
#
|
578
|
+
@ole_excel.Workbooks.each { |w| saved << w.Saved }
|
579
|
+
@ole_excel.CalculateBeforeSave = false
|
580
|
+
@ole_excel.Calculation =
|
581
|
+
calculation_mode == :automatic ? XlCalculationAutomatic : XlCalculationManual
|
582
|
+
saved = []
|
583
|
+
@ole_excel.Workbooks.each { |w| saved << w.Saved }
|
584
|
+
end
|
585
|
+
#(1..@ole_excel.Workbooks.Count).each { |i| @ole_excel.Workbooks(i).Saved = true if saved[i - 1] }
|
572
586
|
end
|
573
587
|
end
|
574
588
|
|
@@ -603,21 +617,36 @@ module RobustExcelOle
|
|
603
617
|
|
604
618
|
def set_options(options)
|
605
619
|
for_this_instance(options)
|
606
|
-
end
|
620
|
+
end
|
607
621
|
|
608
622
|
# set options in all workbooks
|
609
623
|
def for_all_workbooks(options)
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
624
|
+
each_workbook(options)
|
625
|
+
end
|
626
|
+
|
627
|
+
def workbooks
|
628
|
+
#ole_workbooks.map {|ole_workbook| ole_workbook.to_reo }
|
629
|
+
ole_workbooks.map {|ole_workbook| workbook_class.new(ole_workbook) }
|
630
|
+
end
|
631
|
+
|
632
|
+
# traverses over all workbooks and sets options if provided
|
633
|
+
def each_workbook(opts = { })
|
634
|
+
ole_workbooks.each do |ow|
|
635
|
+
wb = workbook_class.new(ow, opts)
|
636
|
+
block_given? ? (yield wb) : wb
|
637
|
+
#if block_given?
|
638
|
+
# yield workbook_class.new(ow, opts)
|
639
|
+
#else
|
640
|
+
# workbook_class.new(ow, opts)
|
641
|
+
#end
|
618
642
|
end
|
619
|
-
|
620
|
-
|
643
|
+
end
|
644
|
+
|
645
|
+
def each_workbook_with_index(opts = { }, offset = 0)
|
646
|
+
i = offset
|
647
|
+
ole_workbooks.each do |ow|
|
648
|
+
yield workbook_class.new(ow, opts), i
|
649
|
+
i += 1
|
621
650
|
end
|
622
651
|
end
|
623
652
|
|
@@ -678,7 +707,6 @@ module RobustExcelOle
|
|
678
707
|
if name.to_s[0,1] =~ /[A-Z]/
|
679
708
|
begin
|
680
709
|
raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
|
681
|
-
|
682
710
|
@ole_excel.send(name, *args)
|
683
711
|
rescue WIN32OLERuntimeError => msg
|
684
712
|
if msg.message =~ /unknown property or method/
|