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
@@ -4,23 +4,30 @@ module RobustExcelOle
4
4
 
5
5
  using StringRefinement
6
6
 
7
- class ListRow
7
+ class ListRow < VbaObjects
8
8
 
9
- def initialize(row_number)
10
- @ole_listrow = ole_table.ListRows.Item(row_number)
9
+ attr_reader :ole_tablerow
10
+
11
+ alias ole_object ole_tablerow
12
+
13
+ def initialize(rownumber_or_oletablerow)
14
+ @ole_tablerow = if !rownumber_or_oletablerow.respond_to?(:succ)
15
+ rownumber_or_oletablerow
16
+ else
17
+ ole_table.ListRows.Item(rownumber_or_oletablerow)
18
+ end
11
19
  end
12
20
 
13
21
  # returns the value of the cell with given column name or number
14
22
  # @param [Variant] column number or column name
15
23
  # @return [Variant] value of the cell
16
24
  def [] column_number_or_name
17
- begin
18
- ole_cell = ole_table.Application.Intersect(
19
- @ole_listrow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
20
- ole_cell.Value
21
- rescue WIN32OLERuntimeError
22
- raise TableRowError, "could not determine the value at column #{column_number_or_name}"
23
- end
25
+ ole_cell = ole_table.Application.Intersect(
26
+ @ole_tablerow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
27
+ value = ole_cell.Value
28
+ value.respond_to?(:gsub) ? value.encode('utf-8') : value
29
+ rescue WIN32OLERuntimeError
30
+ raise TableRowError, "could not determine the value at column #{column_number_or_name}\n#{$!.message}"
24
31
  end
25
32
 
26
33
  # sets the value of the cell with given column name or number
@@ -29,49 +36,66 @@ module RobustExcelOle
29
36
  def []=(column_number_or_name, value)
30
37
  begin
31
38
  ole_cell = ole_table.Application.Intersect(
32
- @ole_listrow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
39
+ @ole_tablerow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
33
40
  ole_cell.Value = value
34
41
  rescue WIN32OLERuntimeError
35
- raise TableRowError, "could not assign value #{value.inspect} to cell at column #{column_number_or_name}"
42
+ raise TableRowError, "could not assign value #{value.inspect} to cell at column #{column_number_or_name}\n#{$!.message}"
36
43
  end
37
44
  end
38
45
 
39
46
  # values of the row
40
47
  # @return [Array] values of the row
41
48
  def values
42
- begin
43
- @ole_listrow.Range.Value.first
44
- rescue WIN32OLERuntimeError
45
- raise TableError, "could not read values"
49
+ value = @ole_tablerow.Range.Value
50
+ return value if value==[nil]
51
+ value = if !value.respond_to?(:pop)
52
+ [value]
53
+ elsif value.first.respond_to?(:pop)
54
+ value.first
46
55
  end
56
+ value.map{|v| v.respond_to?(:gsub) ? v.encode('utf-8') : v}
57
+ rescue WIN32OLERuntimeError
58
+ raise TableError, "could not read values\n#{$!.message}"
47
59
  end
48
60
 
49
61
  # sets the values of the row
50
62
  # @param [Array] values of the row
51
- def set_values values
52
- begin
53
- updated_values = self.values
54
- updated_values[0,values.length] = values
55
- @ole_listrow.Range.Value = [updated_values]
56
- values
57
- rescue WIN32OLERuntimeError
58
- raise TableError, "could not set values #{values.inspect}"
59
- end
63
+ def values= values
64
+ updated_values = self.values
65
+ updated_values[0,values.length] = values
66
+ @ole_tablerow.Range.Value = [updated_values]
67
+ values
68
+ rescue WIN32OLERuntimeError
69
+ raise TableError, "could not set values #{values.inspect}\n#{$!.message}"
70
+ end
71
+
72
+ # key-value pairs of the row
73
+ # @return [Hash] key-value pairs of the row
74
+ def keys_values
75
+ ole_table.column_names.zip(values).to_h
60
76
  end
61
77
 
78
+ alias set_values values=
79
+ alias to_a values
80
+ alias to_h keys_values
81
+
62
82
  # deletes the values of the row
63
83
  def delete_values
64
- begin
65
- @ole_listrow.Range.Value = [[].fill(nil,0..(ole_table.ListColumns.Count)-1)]
66
- nil
67
- rescue WIN32OLERuntimeError
68
- raise TableError, "could not delete values"
69
- end
70
- end
84
+ @ole_tablerow.Range.Value = [[].fill(nil,0..(ole_table.ListColumns.Count)-1)]
85
+ nil
86
+ rescue WIN32OLERuntimeError
87
+ raise TableError, "could not delete values\n#{$!.message}"
88
+ end
89
+
90
+ def == other_listrow
91
+ other_listrow.is_a?(ListRow) && other_listrow.values == self.values
92
+ end
71
93
 
72
94
  def method_missing(name, *args)
95
+ # this should not happen:
96
+ raise(TableRowError, "internal error: ole_table not defined") unless self.class.method_defined?(:ole_table)
73
97
  name_str = name.to_s
74
- core_name = name_str[-1]!='=' ? name_str : name_str[0..-2]
98
+ core_name = name_str.chomp('=')
75
99
  column_names = ole_table.HeaderRowRange.Value.first
76
100
  column_name = column_names.find do |c|
77
101
  c == core_name ||
@@ -82,9 +106,7 @@ module RobustExcelOle
82
106
  c.replace_umlauts.underscore.gsub(/\W/,'_') == core_name
83
107
  end
84
108
  if column_name
85
- appended_eq = (name_str[-1]!='=' ? "" : "=")
86
- method_name = core_name.replace_umlauts.underscore + appended_eq
87
- define_and_call_method(column_name,method_name,*args)
109
+ define_and_call_method(column_name, name, *args)
88
110
  else
89
111
  super(name, *args)
90
112
  end
@@ -97,14 +119,15 @@ module RobustExcelOle
97
119
 
98
120
  # @private
99
121
  def inspect
100
- "#<ListRow: " + "index:#{@ole_listrow.Index}" + " size:#{ole_table.ListColumns.Count}" + " #{ole_table.Name}" + ">"
122
+ "#<ListRow: index:#{@ole_tablerow.Index} size:#{ole_table.ListColumns.Count} #{ole_table.Name}>"
101
123
  end
102
124
 
103
125
  private
104
126
 
105
127
  def define_and_call_method(column_name,method_name,*args)
128
+ column_name = column_name.force_encoding('cp850')
106
129
  ole_cell = ole_table.Application.Intersect(
107
- @ole_listrow.Range, ole_table.ListColumns.Item(column_name).Range)
130
+ @ole_tablerow.Range, ole_table.ListColumns.Item(column_name).Range)
108
131
  define_getting_setting_method(ole_cell,method_name)
109
132
  self.send(method_name, *args)
110
133
  end
@@ -122,6 +145,10 @@ module RobustExcelOle
122
145
  end
123
146
 
124
147
  end
148
+
149
+ # @private
150
+ class TableRowError < WorksheetREOError
151
+ end
125
152
 
126
153
  TableRow = ListRow
127
154
 
@@ -20,7 +20,7 @@ module RobustExcelOle
20
20
 
21
21
  def initialize(win32_range, worksheet = nil)
22
22
  @ole_range = win32_range
23
- @worksheet = worksheet ? worksheet.to_reo : worksheet_class.new(self.Parent)
23
+ @worksheet = (worksheet ? worksheet : self.Parent).to_reo
24
24
  end
25
25
 
26
26
  def rows
@@ -32,8 +32,12 @@ module RobustExcelOle
32
32
  end
33
33
 
34
34
  def each
35
- @ole_range.each_with_index do |ole_cell, index|
36
- yield cell(index){ole_cell}
35
+ if block_given?
36
+ @ole_range.lazy.each_with_index do |ole_cell, index|
37
+ yield cell(index){ole_cell}
38
+ end
39
+ else
40
+ to_enum(:each).lazy
37
41
  end
38
42
  end
39
43
 
@@ -71,68 +75,90 @@ module RobustExcelOle
71
75
  end
72
76
  end
73
77
 
74
- # returns flat array of the values of a given range
78
+ # returns values of a given range
75
79
  # @returns [Array] values of the range (as a nested array)
80
+ =begin
76
81
  def value
77
- begin
78
- if !::RANGES_JRUBY_BUG
79
- self.Value
80
- else
81
- values = []
82
- rows.each do |r|
83
- values_col = []
84
- columns.each{ |c| values_col << worksheet.Cells(r,c).Value}
85
- values << values_col
82
+ value = begin
83
+ if !::RANGES_JRUBY_BUG
84
+ ole_range.Value[0,[ole_range.Rows.Count,worksheet.last_row].min].inject([]) do |res, row|
85
+ res << (!row.nil? ? row[0,[ole_range.Columns.Count,worksheet.last_column].min] : nil)
86
86
  end
87
- values
87
+ else
88
+ # optimization is possible here
89
+ rows_used_range = [rows, last_row].min
90
+ columns_used_rage = [columns, last_column].min
91
+ values = rows_used_range.map{|r| columns_used_range.map {|c| worksheet.Cells(r,c).Value} }
92
+ (values.size==1 && values.first.size==1) ? values.first.first : values
88
93
  end
89
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
90
- raise RangeNotEvaluatable, 'cannot read value'
91
- end
94
+ rescue
95
+ raise RangeNotEvaluatable, "cannot evaluate range #{self.inspect}\n#{$!.message}"
96
+ end
97
+ if value == -2146828288 + RobustExcelOle::XlErrName
98
+ raise RangeNotEvaluatable, "cannot evaluate range #{self.inspect}"
99
+ end
100
+ value
101
+ end
102
+ =end
92
103
 
104
+ def value
105
+ value = begin
106
+ if !::RANGES_JRUBY_BUG
107
+ ole_range.Application.Intersect(ole_range, worksheet.Range(
108
+ worksheet.Cells(1,1),worksheet.Cells(worksheet.last_row,worksheet.last_column))).Value
109
+ else
110
+ # optimization is possible here
111
+ rows_used_range = [rows, last_row].min
112
+ columns_used_rage = [columns, last_column].min
113
+ values = rows_used_range.map{|r| columns_used_range.map {|c| worksheet.Cells(r,c).Value} }
114
+ (values.size==1 && values.first.size==1) ? values.first.first : values
115
+ end
116
+ rescue
117
+ raise RangeNotEvaluatable, "cannot evaluate range #{self.inspect}\n#{$!.message}"
118
+ end
119
+ if value == -2146828288 + RobustExcelOle::XlErrName
120
+ raise RangeNotEvaluatable, "cannot evaluate range #{self.inspect}"
121
+ end
122
+ value
93
123
  end
94
124
 
95
125
  # sets the values if the range
96
126
  # @param [Variant] value
97
127
  def value=(value)
98
- begin
99
- if !::RANGES_JRUBY_BUG
100
- ole_range.Value = value
101
- else
102
- rows.each_with_index do |r,i|
103
- columns.each_with_index do |c,j|
104
- ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
105
- end
128
+ if !::RANGES_JRUBY_BUG
129
+ ole_range.Value = value
130
+ else
131
+ rows.each_with_index do |r,i|
132
+ columns.each_with_index do |c,j|
133
+ ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:pop) ? value[i][j] : value)
106
134
  end
107
135
  end
108
- value
109
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
110
- raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}"
111
136
  end
137
+ value
138
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
139
+ raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}\n#{$!.message}"
112
140
  end
113
141
 
114
- alias_method :v, :value
115
- alias_method :v=, :value=
142
+ alias v value
143
+ alias v= value=
116
144
 
117
145
  # sets the values if the range with a given color
118
146
  # @param [Variant] value
119
147
  # @option opts [Symbol] :color the color of the cell when set
120
148
  def set_value(value, opts = { })
121
- begin
122
- if !::RANGES_JRUBY_BUG
123
- ole_range.Value = value
124
- else
125
- rows.each_with_index do |r,i|
126
- columns.each_with_index do |c,j|
127
- ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
128
- end
149
+ if !::RANGES_JRUBY_BUG
150
+ ole_range.Value = value
151
+ else
152
+ rows.each_with_index do |r,i|
153
+ columns.each_with_index do |c,j|
154
+ ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:pop) ? value[i][j] : value)
129
155
  end
130
156
  end
131
- ole_range.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
132
- value
133
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
134
- raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}"
135
157
  end
158
+ ole_range.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
159
+ value
160
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
161
+ raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}\n#{$!.message}"
136
162
  end
137
163
 
138
164
  # copies a range
@@ -144,52 +170,61 @@ module RobustExcelOle
144
170
  options = { }
145
171
  remaining_args.each do |arg|
146
172
  case arg
147
- when Object::Range, Integer then dest_address = [dest_address,arg]
173
+ when ::Range, Integer then dest_address = [dest_address,arg]
148
174
  when Worksheet, WIN32OLE then dest_sheet = arg.to_reo
149
175
  when Hash then options = arg
150
176
  else raise RangeNotCopied, "cannot copy range: argument error: #{remaining_args.inspect}"
151
177
  end
152
178
  end
153
- begin
154
- rows, columns = address_tool.as_integer_ranges(dest_address)
155
- dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
156
- dest_range_address = if (not dest_address_is_position)
157
- [rows.min..rows.max,columns.min..columns.max]
179
+ dest_range_address = destination_range(dest_address, dest_sheet, options)
180
+ dest_range = dest_sheet.range(dest_range_address)
181
+ if options[:values_only]
182
+ dest_range.v = !options[:transpose] ? self.v : self.v.transpose
183
+ else
184
+ copy_ranges(dest_address, dest_range, dest_range_address, dest_sheet, options)
185
+ end
186
+ dest_range
187
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
188
+ raise RangeNotCopied, "cannot copy range\n#{$!.message}"
189
+ end
190
+
191
+ private
192
+
193
+ def destination_range(dest_address, dest_sheet, options)
194
+ rows, columns = address_tool.as_integer_ranges(dest_address)
195
+ dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
196
+ if !dest_address_is_position
197
+ [rows.min..rows.max,columns.min..columns.max]
198
+ else
199
+ ole_rows, ole_columns = self.Rows, self.Columns
200
+ [rows.min..rows.min + (options[:transpose] ? ole_columns : ole_rows).Count - 1,
201
+ columns.min..columns.min + (options[:transpose] ? ole_rows : ole_columns).Count - 1]
202
+ end
203
+ end
204
+
205
+ def copy_ranges(dest_address, dest_range, dest_range_address, dest_sheet, options)
206
+ workbook = @worksheet.workbook
207
+ if dest_range.worksheet.workbook.excel == workbook.excel
208
+ if options[:transpose]
209
+ self.Copy
210
+ dest_range.PasteSpecial(XlPasteAll,XlPasteSpecialOperationNone,false,true)
158
211
  else
159
- if (not options[:transpose])
160
- [rows.min..rows.min+self.Rows.Count-1, columns.min..columns.min+self.Columns.Count-1]
161
- else
162
- [rows.min..rows.min+self.Columns.Count-1, columns.min..columns.min+self.Rows.Count-1]
163
- end
164
- end
165
- dest_range = dest_sheet.range(dest_range_address)
166
- if options[:values_only]
167
- dest_range.v = options[:transpose] ? self.v.transpose : self.v
212
+ self.Copy(dest_range.ole_range)
213
+ end
214
+ else
215
+ if options[:transpose]
216
+ added_sheet = workbook.add_sheet
217
+ copy(dest_address, added_sheet, transpose: true)
218
+ added_sheet.range(dest_range_address).copy(dest_address,dest_sheet)
219
+ workbook.excel.with_displayalerts(false) {added_sheet.Delete}
168
220
  else
169
- if dest_range.worksheet.workbook.excel == @worksheet.workbook.excel
170
- if options[:transpose]
171
- self.Copy
172
- dest_range.PasteSpecial(XlPasteAll,XlPasteSpecialOperationNone,false,true)
173
- else
174
- self.Copy(dest_range.ole_range)
175
- end
176
- else
177
- if options[:transpose]
178
- added_sheet = @worksheet.workbook.add_sheet
179
- self.copy(dest_address, added_sheet, :transpose => true)
180
- added_sheet.range(dest_range_address).copy(dest_address,dest_sheet)
181
- @worksheet.workbook.excel.with_displayalerts(false) {added_sheet.Delete}
182
- else
183
- self.Copy
184
- dest_sheet.Paste(dest_range.ole_range)
185
- end
186
- end
221
+ self.Copy
222
+ dest_sheet.Paste(dest_range.ole_range)
187
223
  end
188
- dest_range
189
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
190
- raise RangeNotCopied, 'cannot copy range'
191
224
  end
192
225
  end
226
+
227
+ public
193
228
 
194
229
  def == other_range
195
230
  other_range.is_a?(Range) &&
@@ -214,7 +249,7 @@ module RobustExcelOle
214
249
 
215
250
  # @private
216
251
  def to_s
217
- "#<REO::Range: " + "#{@ole_range.Address('External' => true).gsub(/\$/,'')} " + ">"
252
+ "#<REO::Range: #{@ole_range.Address(External: true).gsub(/\$/,'')} >"
218
253
  end
219
254
 
220
255
  # @private
@@ -245,22 +280,19 @@ module RobustExcelOle
245
280
  private
246
281
 
247
282
  def method_missing(name, *args)
248
- if name.to_s[0,1] =~ /[A-Z]/
249
- if ::ERRORMESSAGE_JRUBY_BUG
250
- begin
251
- @ole_range.send(name, *args)
252
- rescue Java::OrgRacobCom::ComFailException
253
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
254
- end
255
- else
256
- begin
257
- @ole_range.send(name, *args)
258
- rescue NoMethodError
259
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
260
- end
283
+ super unless name.to_s[0,1] =~ /[A-Z]/
284
+ if ::ERRORMESSAGE_JRUBY_BUG
285
+ begin
286
+ @ole_range.send(name, *args)
287
+ rescue Java::OrgRacobCom::ComFailException
288
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
261
289
  end
262
290
  else
263
- super
291
+ begin
292
+ @ole_range.send(name, *args)
293
+ rescue NoMethodError
294
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
295
+ end
264
296
  end
265
297
  end
266
298
  end