robust_excel_ole 1.31 → 1.32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +20 -1
  3. data/README.rdoc +118 -18
  4. data/___dummy_workbook.xls +0 -0
  5. data/benchmarking/creek_example.rb +1 -1
  6. data/benchmarking/roo_example.rb +1 -1
  7. data/benchmarking/simple_xlsx_reader_example.rb +1 -1
  8. data/benchmarking/spreadsheet_example.rb +1 -1
  9. data/docs/README_excel.rdoc +16 -24
  10. data/docs/README_listobjects.rdoc +176 -0
  11. data/docs/README_open.rdoc +12 -12
  12. data/docs/README_ranges.rdoc +72 -55
  13. data/docs/README_save_close.rdoc +3 -3
  14. data/docs/README_sheet.rdoc +18 -13
  15. data/examples/example_ruby_library.rb +2 -2
  16. data/examples/introductory_examples/example_range.rb +2 -2
  17. data/examples/modifying_sheets/example_access_sheets_and_cells.rb +6 -6
  18. data/examples/modifying_sheets/example_add_names.rb +1 -1
  19. data/examples/modifying_sheets/example_concating.rb +1 -1
  20. data/examples/modifying_sheets/example_copying.rb +2 -2
  21. data/examples/modifying_sheets/example_listobjects.rb +86 -0
  22. data/examples/modifying_sheets/example_naming.rb +1 -1
  23. data/examples/modifying_sheets/example_ranges.rb +1 -1
  24. data/examples/open_save_close/example_control_to_excel.rb +1 -1
  25. data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
  26. data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
  27. data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
  28. data/examples/open_save_close/example_if_unsaved_forget.rb +3 -3
  29. data/examples/open_save_close/example_if_unsaved_forget_more.rb +4 -4
  30. data/examples/open_save_close/example_read_only.rb +1 -1
  31. data/examples/open_save_close/example_simple.rb +1 -1
  32. data/examples/open_save_close/example_unobtrusively.rb +3 -3
  33. data/lib/robust_excel_ole/address_tool.rb +54 -44
  34. data/lib/robust_excel_ole/base.rb +4 -6
  35. data/lib/robust_excel_ole/bookstore.rb +2 -16
  36. data/lib/robust_excel_ole/cell.rb +16 -21
  37. data/lib/robust_excel_ole/excel.rb +131 -186
  38. data/lib/robust_excel_ole/general.rb +82 -55
  39. data/lib/robust_excel_ole/list_object.rb +182 -109
  40. data/lib/robust_excel_ole/list_row.rb +65 -38
  41. data/lib/robust_excel_ole/range.rb +125 -93
  42. data/lib/robust_excel_ole/range_owners.rb +52 -66
  43. data/lib/robust_excel_ole/version.rb +1 -1
  44. data/lib/robust_excel_ole/workbook.rb +168 -176
  45. data/lib/robust_excel_ole/worksheet.rb +177 -141
  46. data/robust_excel_ole.gemspec +4 -3
  47. data/spec/bookstore_spec.rb +2 -3
  48. data/spec/cell_spec.rb +9 -9
  49. data/spec/data/more_data/workbook.xls +0 -0
  50. data/spec/excel_spec.rb +132 -85
  51. data/spec/general_spec.rb +47 -15
  52. data/spec/list_object_spec.rb +258 -145
  53. data/spec/list_row_spec.rb +218 -0
  54. data/spec/range_spec.rb +76 -29
  55. data/spec/spec_helper.rb +15 -1
  56. data/spec/workbook_spec.rb +75 -34
  57. data/spec/workbook_specs/workbook_all_spec.rb +2 -1
  58. data/spec/workbook_specs/workbook_misc_spec.rb +20 -13
  59. data/spec/workbook_specs/workbook_open_spec.rb +47 -45
  60. data/spec/workbook_specs/workbook_save_spec.rb +21 -22
  61. data/spec/workbook_specs/workbook_sheet_spec.rb +3 -3
  62. data/spec/workbook_specs/workbook_unobtr_spec.rb +303 -303
  63. data/spec/worksheet_spec.rb +522 -318
  64. metadata +37 -2
@@ -0,0 +1,176 @@
1
+ = RobustExcelOle
2
+
3
+ == List Objects
4
+
5
+ === Creating List Objects
6
+
7
+ We can define a list object (or table) from scratch.
8
+
9
+ table = ListObject.new(worksheet, "table 1", [1,1], 3, ["Person","AmountSales"])
10
+
11
+ This command creates a list object in worksheet named "table 1", with upper left corner at position [1,1] (first cell), with 3 rows and the columns "Person" and "AmountSales". Please note, that creating a table this way does work for more than one rows only.
12
+
13
+ Likewise we can get a given list object in a worksheet by providing its table number or name.
14
+
15
+ table = worksheet.table(1)
16
+
17
+ or
18
+
19
+ table = worksheet.table("table1")
20
+
21
+ Now we have a RobustExcelOle ListObject that wraps a WIN32OLE ListObject. So we can send any WIN32OLE (VBA) method to it. See
22
+ https://docs.microsoft.com/en-us/office/vba/api/excel.listobject#methods.
23
+
24
+ === Accessing List Rows
25
+
26
+ A row in this table (list row object) can be accessed with help of #[], given either the row number or a key. The key is a hash of the key column names and the values.
27
+
28
+ row1 = table[1]
29
+
30
+ row1 = table[{"Number" => 1, "Person" => "John"}]
31
+
32
+ If you want to get more than one table row objects that match the key, then you can supply the maximal number of matches. If you want to get all matches, then you state +nil+.
33
+
34
+ rows = table[{"Number" => 1}, limit: 2]
35
+ rows = table[{"Number" => 1}, limit: nil]
36
+
37
+ Additionally the enumerator method +each+ is being provided. So you can also traverse through the listrows.
38
+
39
+ table.each{ |listrow| puts listrow }
40
+
41
+ So we get a RobustExcelOle ListRow objects that wraps a WIN32OLE ListRow. Now we can send any WIN32OLE (VBA) method to it. See
42
+ https://docs.microsoft.com/en-us/office/vba/api/excel.listrow#methods.
43
+
44
+ === Reading and setting values
45
+
46
+ Now we can set value of a cell of the table with help of methods that are equal to or are underscored variants of the column names, e.g.
47
+
48
+ row1.AmountSales = 40
49
+
50
+ or
51
+
52
+ row1.amount_sales = 40
53
+
54
+ or
55
+
56
+ row1["AmountSales"] = 40
57
+
58
+ Similarly you can get the values.
59
+
60
+ row1.AmountSales
61
+ # => 40
62
+
63
+ or
64
+
65
+ row1.amount_sales
66
+ # => 40
67
+
68
+ or
69
+
70
+ row1["AmountSales"]
71
+ # => 40
72
+
73
+ We can also read the values in a whole row.
74
+
75
+ table[1].to_a
76
+ # => ["John", 40]
77
+
78
+ or
79
+
80
+ table[1].values
81
+ # => ["John", 40]
82
+
83
+ or
84
+
85
+ table.row_values(1)
86
+ # => ["John", 40]
87
+
88
+ You can get the column name-value pairs by
89
+
90
+ table[1].to_h
91
+ # => {"Person" => "John", "AmountSales" => 40}
92
+
93
+ We can set the values in a whole row.
94
+
95
+ table[1].values = ["Herber", 80]
96
+
97
+ or
98
+
99
+ table[1].set_values(["Herbert", 80])
100
+
101
+ or
102
+
103
+ table.set_row_values(1, ["Herbert", 80])
104
+
105
+ If the number of given values is less than the number of cells in the row, only the first values are written. The remaining values keep their value.
106
+
107
+ Similarly, we can read and set the values in a whole column, e.g.
108
+
109
+ table.column_values("Person")
110
+ # => ["John", "Peter"]
111
+
112
+ and
113
+
114
+ table.set_column_values(1, ["Herbert","Paul"])
115
+
116
+ The column names we can get with help of
117
+
118
+ table.column_names
119
+
120
+ A column can be renamed.
121
+
122
+ table.rename_column("Person", "Enterprise")
123
+
124
+ or
125
+
126
+ table.rename_column(1, "Enterprise")
127
+
128
+ === Table values
129
+
130
+ We can get the values of the table with help of the method +value+:
131
+
132
+ table.value
133
+
134
+ === Adding and Deleting rows and columns
135
+
136
+ We can add rows and columns, supplying optionally their name, the position and contents.
137
+
138
+ table.add_column("column_name")
139
+ table.add_column("column_name", 3)
140
+ table.add_column("column_name", 3, ["John", "Paul"])
141
+
142
+ table.add_row(3)
143
+ table.add_row(3, ["John", 40, 2, 2004])
144
+
145
+ We can delete only the contents of a column
146
+
147
+ table.delete_column_values("column_name")
148
+
149
+ Similarly can delete only the contents of a row.
150
+
151
+ table.delete_row_values(2)
152
+
153
+ or
154
+
155
+ table[2].delete_values
156
+
157
+ Finally we can delete empty rows and columns.
158
+
159
+ table.delete_empty_rows
160
+ table.delete_empty_columns
161
+
162
+ === Finding values and sorting
163
+
164
+ You can find all cells containing a given value, e.g.
165
+
166
+ table.find_value(value)
167
+ #=> [#<Cell: (5,8)>#, #<Cell: (9,6)>#]
168
+
169
+ You can sort a table according to a given column and sort order, e.g.
170
+
171
+ table.sort("Person", :ascending)
172
+
173
+ == Code
174
+
175
+ listobjects.rb[https://github.com/Thomas008/robust_excel_ole/blob/master/lib/robust_excel_ole/listobjects.rb]
176
+
@@ -85,7 +85,7 @@ Here are a few examples:
85
85
 
86
86
  If you want to open a workbook that was not opened before, or reopen a workbook that was open in an Excel instance that is now closed, in the current (active) Excel instance, then use
87
87
 
88
- workbook = Workbook.open('spec/data/workbook.xls', :default => {:excel => :current})
88
+ workbook = Workbook.open('spec/data/workbook.xls', default: {excel: :current})
89
89
 
90
90
  or
91
91
 
@@ -93,41 +93,41 @@ or
93
93
 
94
94
  In case you want to open such a workbook in a new Excel instance, then use
95
95
 
96
- workbook = Workbook.open('spec/data/workbook.xls', :default => {:excel => :new})
96
+ workbook = Workbook.open('spec/data/workbook.xls', default: {excel: :new})
97
97
 
98
98
  If you want to open a workbook in a new Excel instance, no matter if it was opened before, you can write
99
99
 
100
- workbook = Workbook.open('spec/data/workbook.xls', :force => {:excel => :new})
100
+ workbook = Workbook.open('spec/data/workbook.xls', force: {excel: :new})
101
101
 
102
102
  For simplicity, you can also leave out the +:force+ option (but not the +:default+ option).
103
103
 
104
- workbook = Workbook.open('spec/data/workbook.xls', :excel => :new)
104
+ workbook = Workbook.open('spec/data/workbook.xls', excel: :new)
105
105
 
106
106
  You can also specify an Excel instance
107
107
 
108
108
  excel1 = Excel.create
109
- workbook = Workbook.open('spec/data/workbook.xls', :excel => excel1)
109
+ workbook = Workbook.open('spec/data/workbook.xls', excel: excel1)
110
110
 
111
111
  If you want to open the workbook and make its window visible, then use
112
112
 
113
- book = Workbook.open('spec/data/workbook.xls', :visible => true)
113
+ book = Workbook.open('spec/data/workbook.xls', visible: true)
114
114
 
115
115
  Notice, that when the workbook is visible, the DisplayAlerts of the respective Excel instance is true, if not explicitely DisplayAlerts is set to false in this Excel instance.
116
116
  You can combine options, e.g.
117
117
 
118
- workbook = Workbook.open('spec/data/workbook.xls', :visible => true, :default => {:excel => excel1})
118
+ workbook = Workbook.open('spec/data/workbook.xls', visible: true, default: {excel: excel1})
119
119
 
120
120
  You can use the abbreviations, e.g. in this case
121
121
 
122
- workbook = Workbook.open('spec/data/workbook.xls', :v => true, :d => {:e => excel1})
122
+ workbook = Workbook.open('spec/data/workbook.xls', v: true, d => {e => excel1})
123
123
 
124
124
  If a workbook contains unsaved changes and a workbook with the same filename shall be opened, then the option +:if_unsaved+ manages this conflict. For example, if the workbook with the unsaved changes shall remain open, you can use
125
125
 
126
- workbook = Workbook.open('spec/data/workbook.xls', :if_unsaved => :accept)
126
+ workbook = Workbook.open('spec/data/workbook.xls', if_unsaved: :accept)
127
127
 
128
128
  If a workbook is open and a workbook with the same name but in different path shall be opened, i.e. the first workbook blocks opening the other workbook, then the option +:if_blocked+ handles this situation, e.g.
129
129
 
130
- workbook = Workbook.open('path/workbook.xls', :if_blocked => :forget)
130
+ workbook = Workbook.open('path/workbook.xls', if_blocked: :forget)
131
131
 
132
132
  Remarks:
133
133
 
@@ -170,11 +170,11 @@ The method +to_reo+ uses the method +new+. You can apply the method +new+ direct
170
170
 
171
171
  You can supply options, e.g. +:visible+.
172
172
 
173
- workbook = Workbook.new(win32ole_workbook, :visible => true)
173
+ workbook = Workbook.new(win32ole_workbook, visible: true)
174
174
 
175
175
  You can also supply a workbook and options, e.g.
176
176
 
177
- new_workbook = Workbook.new(workbook, :visible => true)
177
+ new_workbook = Workbook.new(workbook, visible: true)
178
178
 
179
179
  === Identity transperence ===
180
180
 
@@ -8,7 +8,7 @@ RobustExcelOle enables to read and write the contents of ranges and cells in wor
8
8
 
9
9
  Suppose you have opened a workbook.
10
10
 
11
- workbook = Workbook.open('spec/data/workbook.xls', :visible => true)
11
+ workbook = Workbook.open('spec/data/workbook.xls', visible: true)
12
12
 
13
13
  We access the first worksheet:
14
14
 
@@ -42,10 +42,6 @@ or
42
42
 
43
43
  range = worksheet.range(["A1:D3"])
44
44
 
45
- You can also access a range from a workbook by providing the worksheet
46
-
47
- range = workbook(workbook.sheet(1), [1..3,1..4])
48
-
49
45
  Now you can read the values by
50
46
 
51
47
  range.Value
@@ -61,7 +57,17 @@ or as flat array
61
57
  range.values
62
58
  => ["foo", "workbook", "sheet1", nil, "foo", nil, "foobaaa", nil, "matz", "is", "nice", nil]
63
59
 
64
- You can set values with help of range#Value or range#v, e.g.
60
+ You can get the values of a range directly with help of the method #[], e.g.
61
+
62
+ worksheet[1..3,1..4]
63
+ => [["foo", "workbook", "sheet1", nil], ["foo", nil, "foobaaa", nil], ["matz", "is", "nice", nil]]
64
+
65
+ You can set values with help of the method #[]= ,e.g.
66
+
67
+ worksheet[1..3,1..4] = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
68
+
69
+
70
+ Alternatively you can use #Value=, #value= or #v=, or #set_value, e.g.
65
71
 
66
72
  range.Value = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
67
73
 
@@ -79,11 +85,11 @@ or
79
85
 
80
86
  You can color the range when setting the contents of a range.
81
87
 
82
- range.set_value([[1,2,3,4],[5,6,7,8],[9,10,11,12]], :color => 42)
88
+ range.set_value([[1,2,3,4],[5,6,7,8],[9,10,11,12]], color: 42)
83
89
 
84
90
  Now we copy the range. With help of VBA methods you would do
85
91
 
86
- range.Copy(:destination => sheet.range([4,5]).ole_range)
92
+ range.Copy(destination: sheet.range([4,5]).ole_range)
87
93
 
88
94
  or with help of RobustExcelOle
89
95
 
@@ -91,7 +97,7 @@ or with help of RobustExcelOle
91
97
 
92
98
  You can also copy a range into another worksheet in another workbook.
93
99
 
94
- workbook2 = Workbook.open('spec/data/another_workbook.xls', :excel => :new, :visible => true)
100
+ workbook2 = Workbook.open('spec/data/another_workbook.xls', excel: :new, visible: true)
95
101
  range.copy([4,5],book2.sheet(3))
96
102
 
97
103
  Now we define a name that refers to a range consisting of only the first cell, i.e. the 1st row and 1st column. Using VBA methods, you can use
@@ -131,7 +137,7 @@ Finally we can rename a range, and delete the name of a range. With help of VBA
131
137
 
132
138
  Using RobustExcelOle, we write
133
139
 
134
- workbook.rename_range("name", "new_name")
140
+ workbook.rename_name("name", "new_name")
135
141
  workbook.delete_name("name")
136
142
 
137
143
  Now we can read the value of cell simply by providing the row and the column
@@ -140,14 +146,9 @@ Now we can read the value of cell simply by providing the row and the column
140
146
 
141
147
  or with RobustExcelOle
142
148
 
143
- worksheet[1,1].Value
149
+ worksheet[1,1]
144
150
  => "foo
145
151
 
146
- or
147
-
148
- worksheet[1,1].v
149
- => "foo"
150
-
151
152
  Similarly, you can write a cell.
152
153
 
153
154
  worksheet.Cells.Item(1,1).Value = "new_value"
@@ -168,11 +169,11 @@ For example, you can access a range consisting of one cell by providing the row
168
169
 
169
170
  Using the A1-format and R1C1-format you write
170
171
 
171
- range = worksheet.range("A1")
172
+ range = worksheet.range(["A1"])
172
173
 
173
174
  and
174
175
 
175
- range = worksheet.range("Z1S1")
176
+ range = worksheet.range(["Z1S1"])
176
177
 
177
178
  respectively.
178
179
 
@@ -186,55 +187,74 @@ or using the A1-format.
186
187
 
187
188
  or
188
189
 
189
- range = worksheet.range("A1:D3")
190
+ range = worksheet.range(["A1:D3"])
190
191
 
191
192
  or using the R1C1-format
192
193
 
193
- range = worksheet.range("Z1S1:Z3S4")
194
+ range = worksheet.range(["Z1S1:Z3S4"])
194
195
 
195
- Infinite ranges are defined, e.g., by setting the rows or columns to +nil+
196
+ Ranges containing infinite rows or columns can be defined, e.g., by setting the other parameter (columns or row) to +nil+: For example, the rows 1 to 3 you get by
196
197
 
197
198
  range = worksheet.range([1..3,nil])
199
+
200
+ The columns "A" to "C" you get with help of
201
+
198
202
  range = worksheet.range([nil,"A".."B"])
199
203
 
200
- Using the A1-format you write
204
+ You can yield a row also simply by providing the row number, e.g.
205
+
206
+ range = worksheet.range(1)
207
+
208
+ You can also use the A1-format, e.g.
201
209
 
202
- range = worksheet.range("1:3")
203
- range = worksheet.range("A:B")
210
+ range = worksheet.range(["1:3"])
211
+ range = worksheet.range(["A:B"])
204
212
 
205
213
  You can also apply relative references by using brackets, e.g.
206
214
 
207
- range = worksheet.range("Z[1]S1:Z3S[4]")
215
+ range = worksheet.range(["Z[1]S1:Z3S[4]"])
208
216
 
209
217
  or
210
218
 
211
219
  range = worksheet.range([[1]..3,2..[4]])
212
220
 
213
- You can access a range also from a workbook by providing the respective worksheet
221
+ You can access a range via its defined name with
214
222
 
215
- range = workbook(worksheet, [[1..3],2..[4]])
223
+ range = worksheet.range("name")
216
224
 
217
- You get the values of the range as flat array with help of
225
+ === Getting and setting the value of a range
218
226
 
219
- range.values
227
+ You get the value of a range with help of #[] by providing the address of the range, or apply #value on a range, e.g.
220
228
 
221
- You can access a range via its defined name with
229
+ worksheet["name"]
222
230
 
223
- range = worksheet.range("name")
231
+ or
232
+
233
+ worksheet[1..2,3..4]
224
234
 
225
235
  or
226
236
 
227
- range = workbook(worksheet, "name")
237
+ worksheet.range("name").value
238
+
239
+ or
240
+
241
+ worksheet.range([1..2,3..4]).value
242
+
243
+ The value is being restricted to the used range.
244
+
245
+ If you want the values of the range as flat array, then use #values, e.g.
246
+
247
+ range.values
228
248
 
229
249
  === Copying a range
230
250
 
231
251
  Let's assume, you have a source range
232
252
 
233
- range = worksheet.range(1..2,3..5)
253
+ range = worksheet.range([1..2,3..5])
234
254
 
235
255
  or, in A1-format,
236
256
 
237
- range = worksheet.range("C1:E2")
257
+ range = worksheet.range(["C1:E2"])
238
258
 
239
259
  To copy it to the destination range (3..4,6..8), you can use
240
260
 
@@ -250,7 +270,7 @@ You can copy the range into another worksheet of the same or another workbook, e
250
270
 
251
271
  Moreover, you can state, whether you want to copy the values only, and whether you want to transpose the destination range.
252
272
 
253
- range.copy([3,6], destination_range, :values_only => true, :transpose => true)
273
+ range.copy([3,6], destination_range, values_only: true, transpose: true)
254
274
 
255
275
  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.
256
276
 
@@ -300,13 +320,21 @@ or
300
320
 
301
321
  workbook.add_name("name", [1..[2],[1]..4])
302
322
 
303
- You can do the same for an worksheet or Excel object.
323
+ You can do the same for an worksheet.
324
+ You get all names defined in the workbook or worksheet using the method +names+.
304
325
 
305
- === Reading and writing the contents of a named range in a workbook.
326
+ worksheet.names
327
+ # => ["Sheet1!name"]
328
+
329
+ workbook.names
330
+ # => ["Sheet1!name", "four"]
331
+
332
+
333
+ === Reading and writing the contents of a range
306
334
 
307
335
  Assume you have opened a workbook:
308
336
 
309
- workbook = Workbook.open('spec/data/workbook.xls', :visible => true)
337
+ workbook = Workbook.open('spec/data/workbook.xls', visible: true)
310
338
 
311
339
  You can get the contents of a range with a defined name with help of the method [] or +namevalue_glob+.
312
340
 
@@ -320,7 +348,7 @@ or
320
348
 
321
349
  Using +namevalue_glob+, via the option +:default+ you can provide a value that is returned when the name cannot be found or some other error would occur.
322
350
 
323
- workbook.namvalue_glob("name", :default => "default_value")
351
+ workbook.namvalue_glob("name", default: "default_value")
324
352
 
325
353
  You can set the contents of a range with
326
354
 
@@ -332,26 +360,16 @@ or
332
360
 
333
361
  You can color the range when setting the contents of a range.
334
362
 
335
- workbook.set_namevalue_glob("name", "new_value", :color => 4)
363
+ workbook.set_namevalue_glob("name", "new_value", color: 4)
336
364
 
337
365
  Similarly, the contents of a named range can be read and modified in a worksheet
338
366
 
339
367
  worksheet = workbook.sheet(1)
340
368
 
341
369
  worksheet["name"]
342
- => value
343
-
344
- worksheet["name"] = "new_value"
345
-
346
- or an Application object.
347
-
348
- excel = book.excel
349
-
350
- excel["name"]
351
- => "value"
352
-
353
- excel["name"] = "new_value"
370
+ # => "old_value"
354
371
 
372
+ worksheet["name"] = "new_value"
355
373
 
356
374
  === Reading and writing the contents of a range with a locally defined name
357
375
 
@@ -375,13 +393,13 @@ or
375
393
 
376
394
  Similarly to namevalue, you can provide a default value that is returned when ocurring an error.
377
395
 
378
- worksheet.namevalue("name", :default => "default_value")
396
+ worksheet.namevalue("name", default: "default_value")
379
397
 
380
398
  === Accessing a cell
381
399
 
382
400
  You can read a cell from a sheet object by providing the row and the column. For example, the following lines provide the value of the first cell (first row, first column):
383
401
 
384
- worksheet[1,1].Value
402
+ worksheet[1,1]
385
403
  => "foo
386
404
 
387
405
  or
@@ -398,7 +416,6 @@ or
398
416
  worksheet.set_cellval(1,1,"new_value")
399
417
 
400
418
 
401
-
402
419
  == Code
403
420
 
404
421
  range.rb[https://github.com/Thomas008/robust_excel_ole/blob/master/lib/robust_excel_ole/range.rb]