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