robust_excel_ole 1.31 → 1.32

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.
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
@@ -11,6 +11,8 @@ module RobustExcelOle
11
11
  # worksheet: see https://github.com/Thomas008/robust_excel_ole/blob/master/lib/robust_excel_ole/worksheet.rb
12
12
  class Worksheet < RangeOwners
13
13
 
14
+ include Enumerable
15
+
14
16
  using ToReoRefinement
15
17
 
16
18
  attr_reader :ole_worksheet
@@ -49,9 +51,9 @@ module RobustExcelOle
49
51
  # sheet name
50
52
  # @returns name of the sheet
51
53
  def name
52
- @ole_worksheet.Name
54
+ @ole_worksheet.Name.encode('utf-8')
53
55
  rescue
54
- raise WorksheetREOError, "name could not be determined"
56
+ raise WorksheetREOError, "name could not be determined\n#{$!.message}"
55
57
  end
56
58
 
57
59
  # sets sheet name
@@ -66,49 +68,69 @@ module RobustExcelOle
66
68
  end
67
69
  end
68
70
 
69
- # returns a cell given the defined name or row and column
70
- # @params row, column, or name
71
- # @returns cell, if row and column are given
72
- def [] p1, p2 = :__not_provided
73
- if p2 != :__not_provided
74
- x, y = p1, p2
75
- xy = "#{x}_#{y}"
76
- @cells = { }
77
- begin
78
- @cells[xy] = RobustExcelOle::Cell.new(@ole_worksheet.Cells.Item(x, y), @worksheet)
79
- rescue
80
- raise RangeNotEvaluatable, "cannot read cell (#{x.inspect},#{y.inspect})"
81
- end
71
+ # value of a range given its defined name or address
72
+ # @params [Variant] defined name or address
73
+ # @returns [Variant] value (contents) of the range
74
+ def [](name_or_address, address2 = :__not_provided)
75
+ range(name_or_address, address2).value
76
+ end
77
+
78
+ # sets the value of a range given its defined name or address, and the value
79
+ # @params [Variant] defined name or address of the range
80
+ # @params [Variant] value (contents) of the range
81
+ # @returns [Variant] value (contents) of the range
82
+ def []=(name_or_address, value_or_address2, remaining_arg = :__not_provided)
83
+ if remaining_arg != :__not_provided
84
+ name_or_address, value = [name_or_address, value_or_address2], remaining_arg
82
85
  else
83
- name = p1
84
- begin
85
- namevalue_global(name)
86
- rescue REOError
87
- namevalue(name)
88
- end
86
+ value = value_or_address2
87
+ end
88
+ begin
89
+ range(name_or_address).value = value
90
+ rescue #WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
91
+ raise RangeNotEvaluatable, "cannot assign value to range with name or address #{name_or_address.inspect}\n#{$!.message}"
89
92
  end
90
93
  end
91
94
 
92
- # sets the value of a cell
93
- # @params row and column, or defined name
94
- def []= (p1, p2, p3 = :__not_provided)
95
- if p3 != :__not_provided
96
- x, y, value = p1, p2, p3
97
- set_cellval(x,y,value)
98
- else
99
- name, value = p1, p2
100
- begin
101
- set_namevalue_global(name, value)
102
- rescue REOError
95
+ # a range given a defined name or address
96
+ # @params [Variant] defined name or address
97
+ # @return [Range] a range
98
+ def range(name_or_address, address2 = :__not_provided)
99
+ if name_or_address.respond_to?(:gsub) && address2 == :__not_provided
100
+ name = name_or_address
101
+ range = get_name_object(name).RefersToRange rescue nil
102
+ end
103
+ unless range
104
+ address = normalize_address(name_or_address, address2)
105
+ workbook.retain_saved do
103
106
  begin
104
- workbook.set_namevalue_global(name, value)
105
- rescue REOError
106
- set_namevalue(name, value)
107
+ self.Names.Add('__dummy001',nil,true,nil,nil,nil,nil,nil,nil,'=' + address_tool.as_r1c1(address))
108
+ range = get_name_object('__dummy001').RefersToRange
109
+ self.Names.Item('__dummy001').Delete
110
+ rescue
111
+ address2_string = (address2.nil? || address2 == :__not_provided) ? "" : ", #{address2.inspect}"
112
+ raise RangeNotCreated, "cannot find name or address #{name_or_address.inspect}#{address2_string})"
107
113
  end
108
114
  end
109
115
  end
116
+ range.to_reo
117
+ end
118
+
119
+ private
120
+
121
+ def normalize_address(address, address2)
122
+ address = [address,address2] unless address2 == :__not_provided
123
+ address = if address.is_a?(Integer) || address.is_a?(::Range)
124
+ [address, nil]
125
+ elsif address.is_a?(Array) && address.size == 1 && (address.first.is_a?(Integer) || address.first.is_a?(::Range))
126
+ [address.first, nil]
127
+ else
128
+ address
129
+ end
110
130
  end
111
131
 
132
+ public
133
+
112
134
  # returns the contents of a range with a locally defined name
113
135
  # evaluates the formula if the contents is a formula
114
136
  # if the name could not be found or the range or value could not be determined,
@@ -117,7 +139,7 @@ module RobustExcelOle
117
139
  # @param [Hash] opts the options
118
140
  # @option opts [Symbol] :default the default value that is provided if no contents could be returned
119
141
  # @return [Variant] the contents of a range with given name
120
- def namevalue(name, opts = { :default => :__not_provided })
142
+ def namevalue(name, opts = { default: :__not_provided })
121
143
  begin
122
144
  ole_range = self.Range(name)
123
145
  rescue # WIN32OLERuntimeError, VBAMethodMissingError, Java::OrgRacobCom::ComFailException
@@ -135,11 +157,11 @@ module RobustExcelOle
135
157
  end
136
158
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
137
159
  return opts[:default] unless opts[:default] == :__not_provided
138
- raise RangeNotEvaluatable, "cannot determine value of range named #{name.inspect} in #{self.inspect}"
160
+ raise RangeNotEvaluatable, "cannot determine value of range named #{name.inspect} in #{self.inspect}\n#{$!.message}"
139
161
  end
140
162
  if value == -2146828288 + RobustExcelOle::XlErrName
141
163
  return opts[:default] unless opts[:default] == __not_provided
142
- raise RangeNotEvaluatable, "cannot evaluate range named #{name.inspect} in #{File.basename(workbook.stored_filename).inspect rescue nil}"
164
+ raise RangeNotEvaluatable, "cannot evaluate range named #{name.inspect} in #{File.basename(workbook.stored_filename).inspect rescue nil}\n#{$!.message}"
143
165
  end
144
166
  return opts[:default] unless (opts[:default] == :__not_provided) || value.nil?
145
167
  value
@@ -164,25 +186,23 @@ module RobustExcelOle
164
186
  row, col = address_tool.as_integer_ranges(address_r1c1)
165
187
  row.each_with_index do |r,i|
166
188
  col.each_with_index do |c,j|
167
- ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
189
+ ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:pop) ? value[i][j] : value)
168
190
  end
169
191
  end
170
192
  end
171
193
  value
172
194
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
173
- raise RangeNotEvaluatable, "cannot assign value to range named #{name.inspect} in #{self.inspect}"
195
+ raise RangeNotEvaluatable, "cannot assign value to range named #{name.inspect} in #{self.inspect}\n#{$!.message}"
174
196
  end
175
197
  end
176
198
 
177
199
  # value of a cell, if row and column are given
178
200
  # @params row and column
179
201
  # @returns value of the cell
180
- def cellval(x,y)
181
- begin
182
- @ole_worksheet.Cells.Item(x, y).Value
183
- rescue
184
- raise RangeNotEvaluatable, "cannot read cell (#{x.inspect},#{y.inspect})"
185
- end
202
+ def cellval(x,y) # :deprecated :#
203
+ @ole_worksheet.Cells.Item(x, y).Value
204
+ rescue
205
+ raise RangeNotEvaluatable, "cannot read cell (#{x.inspect},#{y.inspect})\n#{$!.message}"
186
206
  end
187
207
 
188
208
  # sets the value of a cell, if row, column and color of the cell are given
@@ -193,73 +213,93 @@ module RobustExcelOle
193
213
  cell.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
194
214
  cell.Value = value
195
215
  rescue # WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
196
- raise RangeNotEvaluatable, "cannot assign value #{value.inspect} to cell (#{y.inspect},#{x.inspect})"
216
+ raise RangeNotEvaluatable, "cannot assign value #{value.inspect} to cell (#{y.inspect},#{x.inspect})\n#{$!.message}"
197
217
  end
198
218
 
199
- # provides a 2-dimensional array that contains the values in each row
219
+ # @return [Array] a 2-dimensional array that contains the values in each row of the used range
200
220
  def values
201
221
  @ole_worksheet.UsedRange.Value
202
222
  end
203
223
 
204
- # enumerator for accessing cells
224
+ # @return [Enumerator] traversing the rows values
205
225
  def each
206
- each_row do |row_range|
207
- row_range.each do |cell|
208
- yield cell
209
- end
210
- end
211
- end
212
-
213
- def each_with_index(offset = 0)
214
- i = offset
215
- each_row do |row_range|
216
- row_range.each do |cell|
217
- yield cell, i
218
- i += 1
226
+ if block_given?
227
+ @ole_worksheet.UsedRange.Rows.lazy.each do |ole_row|
228
+ row_value = ole_row.Value
229
+ yield (row_value.nil? ? [] : row_value.first)
219
230
  end
231
+ else
232
+ to_enum(:each).lazy
220
233
  end
221
234
  end
222
235
 
223
- # enumerator for accessing rows
236
+ # @return [Enumerator] traversing the rows
224
237
  def each_row(offset = 0)
225
- offset += 1
226
- 1.upto(@end_row) do |row|
227
- next if row < offset
228
- yield RobustExcelOle::Range.new(@ole_worksheet.Range(@ole_worksheet.Cells(row, 1), @ole_worksheet.Cells(row, @end_column)), self)
238
+ if block_given?
239
+ offset += 1
240
+ 1.upto(@end_row) do |row|
241
+ next if row < offset
242
+ yield RobustExcelOle::Range.new(@ole_worksheet.Range(@ole_worksheet.Cells(row, 1), @ole_worksheet.Cells(row, @end_column)), self)
243
+ end
244
+ else
245
+ to_enum(:each_row).lazy
229
246
  end
230
247
  end
231
248
 
232
- def each_row_with_index(offset = 0)
249
+ def each_row_with_index(offset = 0) # :nodoc: # # :deprecated :#
233
250
  each_row(offset) do |row_range|
234
251
  yield RobustExcelOle::Range.new(row_range, self), (row_range.Row - 1 - offset)
235
252
  end
236
253
  end
237
254
 
238
- # enumerator for accessing columns
255
+ # @return [Enumerator] traversing the columns
239
256
  def each_column(offset = 0)
240
- offset += 1
241
- 1.upto(@end_column) do |column|
242
- next if column < offset
243
- yield RobustExcelOle::Range.new(@ole_worksheet.Range(@ole_worksheet.Cells(1, column), @ole_worksheet.Cells(@end_row, column)), self)
257
+ if block_given?
258
+ offset += 1
259
+ 1.upto(@end_column) do |column|
260
+ next if column < offset
261
+ yield RobustExcelOle::Range.new(@ole_worksheet.Range(@ole_worksheet.Cells(1, column), @ole_worksheet.Cells(@end_row, column)), self)
262
+ end
263
+ else
264
+ to_enum(:each_column).lazy
244
265
  end
245
266
  end
246
267
 
247
- def each_column_with_index(offset = 0)
268
+ def each_column_with_index(offset = 0) # :nodoc: # # :deprecated :#
248
269
  each_column(offset) do |column_range|
249
270
  yield RobustExcelOle::Range.new(column_range, self), (column_range.Column - 1 - offset)
250
271
  end
251
272
  end
252
273
 
274
+ # @return [Enumerator] traversing the cells
275
+ def each_cell
276
+ if block_given?
277
+ each_row do |row_range|
278
+ row_range.lazy.each do |cell|
279
+ yield cell
280
+ end
281
+ end
282
+ else
283
+ to_enum(:each_cell).lazy
284
+ end
285
+ end
286
+
287
+ def each_cell_with_index(offset = 0) # :nodoc: # # :deprecated :#
288
+ i = offset
289
+ each_row do |row_range|
290
+ row_range.each do |cell|
291
+ yield cell, i
292
+ i += 1
293
+ end
294
+ end
295
+ end
296
+
253
297
  def each_rowvalue # :deprecated: #
254
298
  values.each do |row_values|
255
299
  yield row_values
256
300
  end
257
301
  end
258
302
 
259
- def each_value # :deprecated: #
260
- each_rowvalue
261
- end
262
-
263
303
  def each_rowvalue_with_index(offset = 0) # :deprecated: #
264
304
  i = offset
265
305
  values.each do |row_values|
@@ -268,6 +308,8 @@ module RobustExcelOle
268
308
  end
269
309
  end
270
310
 
311
+ alias each_value each_rowvalue # :deprecated: #
312
+
271
313
  def row_range(row, integer_range = nil)
272
314
  integer_range ||= 1..@end_column
273
315
  RobustExcelOle::Range.new(@ole_worksheet.Range(@ole_worksheet.Cells(row, integer_range.min), @ole_worksheet.Cells(row, integer_range.max)), self)
@@ -280,44 +322,17 @@ module RobustExcelOle
280
322
 
281
323
  def == other_worksheet
282
324
  other_worksheet.is_a?(Worksheet) &&
283
- self.workbook == other_worksheet.workbook &&
284
- self.Name == other_worksheet.Name
285
- end
286
-
287
- # creates a range from a given defined name or address
288
- # @params [Variant] defined name or address
289
- # @return [Range] a range
290
- def range(name_or_address, address2 = :__not_provided)
291
- if name_or_address.respond_to?(:gsub) && address2 == :__not_provided
292
- name = name_or_address
293
- range = RobustExcelOle::Range.new(name_object(name).RefersToRange, self) rescue nil
294
- end
295
- unless range
296
- address = name_or_address
297
- address = [name_or_address,address2] unless address2 == :__not_provided
298
- workbook.retain_saved do
299
- begin
300
- self.Names.Add('__dummy001',nil,true,nil,nil,nil,nil,nil,nil,'=' + address_tool.as_r1c1(address))
301
- range = RobustExcelOle::Range.new(name_object('__dummy001').RefersToRange, self)
302
- self.Names.Item('__dummy001').Delete
303
- rescue
304
- address2_string = address2.nil? ? "" : ", #{address2.inspect}"
305
- raise RangeNotCreated, "cannot create range (#{name_or_address.inspect}#{address2_string})"
306
- end
307
- end
308
- end
309
- range
325
+ self.workbook == other_worksheet.workbook &&
326
+ self.Name == other_worksheet.Name
310
327
  end
311
328
 
329
+
312
330
  # @params [Variant] table (listobject) name or number
313
331
  # @return [ListObject] a table (listobject)
314
332
  def table(number_or_name)
315
- begin
316
- ole_listobject = @ole_worksheet.ListObjects.Item(number_or_name)
317
- rescue
318
- raise WorksheetREOError, "table #{number_or_name} not found"
319
- end
320
- ListObject.new(ole_listobject)
333
+ listobject_class.new(@ole_worksheet.ListObjects.Item(number_or_name))
334
+ rescue
335
+ raise WorksheetREOError, "table #{number_or_name} not found"
321
336
  end
322
337
 
323
338
  # @private
@@ -330,6 +345,29 @@ module RobustExcelOle
330
345
  false
331
346
  end
332
347
 
348
+ # last_row, last_column:
349
+ # the last row and last column in a worksheet can be determined with help of
350
+ # UsedRange.SpecialCells and UsedRange.Rows/Columns
351
+ # both values can differ in certain cases:
352
+ # - if the worksheet contains a table, then UsedRange starts at the table, not in the first cell
353
+ # therefore we use SpecialCells.
354
+ # - if the worksheet contains merged cells, then SpecialCells considers the merged cells only,
355
+ # therefor we use UsedRange here.
356
+
357
+ # @private
358
+ def last_row
359
+ special_last_row = @ole_worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Row
360
+ used_last_row = @ole_worksheet.UsedRange.Rows.Count
361
+ [special_last_row, used_last_row].max
362
+ end
363
+
364
+ # @private
365
+ def last_column
366
+ special_last_column = @ole_worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Column
367
+ used_last_column = @ole_worksheet.UsedRange.Columns.Count
368
+ [special_last_column, used_last_column].max
369
+ end
370
+
333
371
  using ParentRefinement
334
372
  using StringRefinement
335
373
 
@@ -348,9 +386,24 @@ module RobustExcelOle
348
386
  self.class.workbook_class
349
387
  end
350
388
 
389
+ # @private
390
+ def self.listobject_class
391
+ @listobject_class ||= begin
392
+ module_name = self.parent_name
393
+ "#{module_name}::ListObject".constantize
394
+ rescue NameError => e
395
+ ListObject
396
+ end
397
+ end
398
+
399
+ # @private
400
+ def listobject_class
401
+ self.class.listobject_class
402
+ end
403
+
351
404
  # @private
352
405
  def to_s
353
- '#<Worksheet: ' + (workbook.nil? ? 'not alive ' : (name + ' ' + File.basename(workbook.stored_filename)).to_s) + ">"
406
+ "#<Worksheet: #{(workbook.nil? ? "not alive " : (name + ' ' + File.basename(workbook.stored_filename)))}>"
354
407
  end
355
408
 
356
409
  # @private
@@ -363,39 +416,22 @@ module RobustExcelOle
363
416
  private
364
417
 
365
418
  def method_missing(name, *args)
366
- if name.to_s[0,1] =~ /[A-Z]/
367
- if ::ERRORMESSAGE_JRUBY_BUG
368
- begin
369
- @ole_worksheet.send(name, *args)
370
- rescue Java::OrgRacobCom::ComFailException
371
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
372
- end
373
- else
374
- begin
375
- @ole_worksheet.send(name, *args)
376
- rescue NoMethodError
377
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
378
- end
419
+ super unless name.to_s[0,1] =~ /[A-Z]/
420
+ if ::ERRORMESSAGE_JRUBY_BUG
421
+ begin
422
+ @ole_worksheet.send(name, *args)
423
+ rescue Java::OrgRacobCom::ComFailException
424
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
379
425
  end
380
426
  else
381
- super
427
+ begin
428
+ @ole_worksheet.send(name, *args)
429
+ rescue NoMethodError
430
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
431
+ end
382
432
  end
383
433
  end
384
-
385
- def last_row
386
- special_last_row = @ole_worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Row
387
- used_last_row = @ole_worksheet.UsedRange.Rows.Count
388
-
389
- special_last_row >= used_last_row ? special_last_row : used_last_row
390
- end
391
-
392
- def last_column
393
- special_last_column = @ole_worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Column
394
- used_last_column = @ole_worksheet.UsedRange.Columns.Count
395
-
396
- special_last_column >= used_last_column ? special_last_column : used_last_column
397
- end
398
-
434
+
399
435
  end
400
436
 
401
437
  public