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