robust_excel_ole 1.27 → 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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +36 -2
  3. data/README.rdoc +121 -19
  4. data/___dummy_workbook.xls +0 -0
  5. data/benchmarking/creek_example.rb +1 -1
  6. data/benchmarking/reo_example.rb +1 -1
  7. data/benchmarking/reo_example1.rb +1 -1
  8. data/benchmarking/reo_example2.rb +1 -1
  9. data/benchmarking/roo_example.rb +1 -1
  10. data/benchmarking/simple_xlsx_reader_example.rb +1 -1
  11. data/benchmarking/spreadsheet_example.rb +1 -1
  12. data/bin/jreo +19 -0
  13. data/bin/reo +19 -0
  14. data/docs/README_excel.rdoc +16 -24
  15. data/docs/README_listobjects.rdoc +176 -0
  16. data/docs/README_open.rdoc +20 -16
  17. data/docs/README_ranges.rdoc +72 -55
  18. data/docs/README_save_close.rdoc +3 -3
  19. data/docs/README_sheet.rdoc +19 -20
  20. data/examples/example_ruby_library.rb +2 -2
  21. data/examples/introductory_examples/example_open.rb +11 -0
  22. data/examples/introductory_examples/example_range.rb +2 -2
  23. data/examples/modifying_sheets/example_access_sheets_and_cells.rb +6 -6
  24. data/examples/modifying_sheets/example_add_names.rb +1 -1
  25. data/examples/modifying_sheets/example_concating.rb +1 -1
  26. data/examples/modifying_sheets/example_copying.rb +2 -2
  27. data/examples/modifying_sheets/example_listobjects.rb +86 -0
  28. data/examples/modifying_sheets/example_naming.rb +1 -1
  29. data/examples/modifying_sheets/example_ranges.rb +1 -1
  30. data/examples/open_save_close/example_control_to_excel.rb +1 -1
  31. data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +1 -1
  32. data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
  33. data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
  34. data/examples/open_save_close/example_if_unsaved_forget.rb +3 -3
  35. data/examples/open_save_close/example_if_unsaved_forget_more.rb +4 -4
  36. data/examples/open_save_close/example_read_only.rb +1 -1
  37. data/examples/open_save_close/example_simple.rb +1 -1
  38. data/examples/open_save_close/example_unobtrusively.rb +3 -3
  39. data/lib/robust_excel_ole.rb +19 -16
  40. data/lib/robust_excel_ole/address_tool.rb +54 -44
  41. data/lib/robust_excel_ole/base.rb +9 -6
  42. data/lib/robust_excel_ole/bookstore.rb +3 -17
  43. data/lib/robust_excel_ole/cell.rb +17 -22
  44. data/lib/robust_excel_ole/cygwin.rb +2 -0
  45. data/lib/robust_excel_ole/excel.rb +136 -201
  46. data/lib/robust_excel_ole/general.rb +249 -238
  47. data/lib/robust_excel_ole/list_object.rb +186 -210
  48. data/lib/robust_excel_ole/list_row.rb +155 -0
  49. data/lib/robust_excel_ole/range.rb +130 -94
  50. data/lib/robust_excel_ole/range_owners.rb +54 -135
  51. data/lib/robust_excel_ole/version.rb +1 -1
  52. data/lib/robust_excel_ole/workbook.rb +230 -196
  53. data/lib/robust_excel_ole/worksheet.rb +254 -133
  54. data/lib/spec_helper.rb +1 -1
  55. data/robust_excel_ole.gemspec +4 -3
  56. data/spec/address_tool_spec.rb +2 -2
  57. data/spec/base_spec.rb +19 -17
  58. data/spec/bookstore_spec.rb +3 -4
  59. data/spec/cell_spec.rb +10 -10
  60. data/spec/cygwin_spec.rb +1 -1
  61. data/spec/data/more_data/workbook.xls +0 -0
  62. data/spec/excel_spec.rb +133 -86
  63. data/spec/general_spec.rb +79 -18
  64. data/spec/list_object_spec.rb +259 -81
  65. data/spec/list_row_spec.rb +218 -0
  66. data/spec/range_spec.rb +75 -41
  67. data/spec/spec_helper.rb +16 -2
  68. data/spec/workbook_spec.rb +87 -46
  69. data/spec/workbook_specs/workbook_all_spec.rb +9 -28
  70. data/spec/workbook_specs/workbook_close_spec.rb +1 -1
  71. data/spec/workbook_specs/workbook_misc_spec.rb +52 -45
  72. data/spec/workbook_specs/workbook_open_spec.rb +103 -50
  73. data/spec/workbook_specs/workbook_save_spec.rb +22 -23
  74. data/spec/workbook_specs/workbook_sheet_spec.rb +4 -4
  75. data/spec/workbook_specs/workbook_subclass_spec.rb +1 -1
  76. data/spec/workbook_specs/workbook_unobtr_spec.rb +553 -395
  77. data/spec/worksheet_spec.rb +544 -308
  78. metadata +38 -3
  79. data/lib/reo_console.rb +0 -42
@@ -0,0 +1,155 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module RobustExcelOle
4
+
5
+ using StringRefinement
6
+
7
+ class ListRow < VbaObjects
8
+
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
19
+ end
20
+
21
+ # returns the value of the cell with given column name or number
22
+ # @param [Variant] column number or column name
23
+ # @return [Variant] value of the cell
24
+ def [] column_number_or_name
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}"
31
+ end
32
+
33
+ # sets the value of the cell with given column name or number
34
+ # @param [Variant] column number or column name
35
+ # @param [Variant] value of the cell
36
+ def []=(column_number_or_name, value)
37
+ begin
38
+ ole_cell = ole_table.Application.Intersect(
39
+ @ole_tablerow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
40
+ ole_cell.Value = value
41
+ rescue WIN32OLERuntimeError
42
+ raise TableRowError, "could not assign value #{value.inspect} to cell at column #{column_number_or_name}\n#{$!.message}"
43
+ end
44
+ end
45
+
46
+ # values of the row
47
+ # @return [Array] values of the row
48
+ def 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
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}"
59
+ end
60
+
61
+ # sets the values of the row
62
+ # @param [Array] values of the row
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
76
+ end
77
+
78
+ alias set_values values=
79
+ alias to_a values
80
+ alias to_h keys_values
81
+
82
+ # deletes the values of the row
83
+ def delete_values
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
93
+
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)
97
+ name_str = name.to_s
98
+ core_name = name_str.chomp('=')
99
+ column_names = ole_table.HeaderRowRange.Value.first
100
+ column_name = column_names.find do |c|
101
+ c == core_name ||
102
+ c.gsub(/\W/,'_') == core_name ||
103
+ c.underscore == core_name ||
104
+ c.underscore.gsub(/\W/,'_') == core_name ||
105
+ c.replace_umlauts.gsub(/\W/,'_') == core_name ||
106
+ c.replace_umlauts.underscore.gsub(/\W/,'_') == core_name
107
+ end
108
+ if column_name
109
+ define_and_call_method(column_name, name, *args)
110
+ else
111
+ super(name, *args)
112
+ end
113
+ end
114
+
115
+ # @private
116
+ def to_s
117
+ inspect
118
+ end
119
+
120
+ # @private
121
+ def inspect
122
+ "#<ListRow: index:#{@ole_tablerow.Index} size:#{ole_table.ListColumns.Count} #{ole_table.Name}>"
123
+ end
124
+
125
+ private
126
+
127
+ def define_and_call_method(column_name,method_name,*args)
128
+ column_name = column_name.force_encoding('cp850')
129
+ ole_cell = ole_table.Application.Intersect(
130
+ @ole_tablerow.Range, ole_table.ListColumns.Item(column_name).Range)
131
+ define_getting_setting_method(ole_cell,method_name)
132
+ self.send(method_name, *args)
133
+ end
134
+
135
+ def define_getting_setting_method(ole_cell,name)
136
+ if name[-1] != '='
137
+ self.class.define_method(name) do
138
+ ole_cell.Value
139
+ end
140
+ else
141
+ self.class.define_method(name) do |value|
142
+ ole_cell.Value = value
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ # @private
150
+ class TableRowError < WorksheetREOError
151
+ end
152
+
153
+ TableRow = ListRow
154
+
155
+ end
@@ -16,10 +16,11 @@ module RobustExcelOle
16
16
 
17
17
  alias ole_object ole_range
18
18
 
19
+ using ToReoRefinement
19
20
 
20
21
  def initialize(win32_range, worksheet = nil)
21
22
  @ole_range = win32_range
22
- @worksheet = worksheet ? worksheet.to_reo : worksheet_class.new(self.Parent)
23
+ @worksheet = (worksheet ? worksheet : self.Parent).to_reo
23
24
  end
24
25
 
25
26
  def rows
@@ -31,8 +32,12 @@ module RobustExcelOle
31
32
  end
32
33
 
33
34
  def each
34
- @ole_range.each_with_index do |ole_cell, index|
35
- 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
36
41
  end
37
42
  end
38
43
 
@@ -70,68 +75,90 @@ module RobustExcelOle
70
75
  end
71
76
  end
72
77
 
73
- # returns flat array of the values of a given range
78
+ # returns values of a given range
74
79
  # @returns [Array] values of the range (as a nested array)
80
+ =begin
75
81
  def value
76
- begin
77
- if !::RANGES_JRUBY_BUG
78
- self.Value
79
- else
80
- values = []
81
- rows.each do |r|
82
- values_col = []
83
- columns.each{ |c| values_col << worksheet.Cells(r,c).Value}
84
- 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)
85
86
  end
86
- 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
87
93
  end
88
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
89
- raise RangeNotEvaluatable, 'cannot read value'
90
- 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
91
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
92
123
  end
93
124
 
94
125
  # sets the values if the range
95
126
  # @param [Variant] value
96
127
  def value=(value)
97
- begin
98
- if !::RANGES_JRUBY_BUG
99
- ole_range.Value = value
100
- else
101
- rows.each_with_index do |r,i|
102
- columns.each_with_index do |c,j|
103
- ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
104
- 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)
105
134
  end
106
135
  end
107
- value
108
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
109
- raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}"
110
136
  end
137
+ value
138
+ rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
139
+ raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}\n#{$!.message}"
111
140
  end
112
141
 
113
- alias_method :v, :value
114
- alias_method :v=, :value=
142
+ alias v value
143
+ alias v= value=
115
144
 
116
145
  # sets the values if the range with a given color
117
146
  # @param [Variant] value
118
147
  # @option opts [Symbol] :color the color of the cell when set
119
148
  def set_value(value, opts = { })
120
- begin
121
- if !::RANGES_JRUBY_BUG
122
- ole_range.Value = value
123
- else
124
- rows.each_with_index do |r,i|
125
- columns.each_with_index do |c,j|
126
- ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
127
- 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)
128
155
  end
129
156
  end
130
- ole_range.Interior.ColorIndex = opts[:color] unless opts[:color].nil?
131
- value
132
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
133
- raise RangeNotEvaluatable, "cannot assign value to range #{self.inspect}"
134
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}"
135
162
  end
136
163
 
137
164
  # copies a range
@@ -143,52 +170,61 @@ module RobustExcelOle
143
170
  options = { }
144
171
  remaining_args.each do |arg|
145
172
  case arg
146
- when Object::Range, Integer then dest_address = [dest_address,arg]
173
+ when ::Range, Integer then dest_address = [dest_address,arg]
147
174
  when Worksheet, WIN32OLE then dest_sheet = arg.to_reo
148
175
  when Hash then options = arg
149
176
  else raise RangeNotCopied, "cannot copy range: argument error: #{remaining_args.inspect}"
150
177
  end
151
178
  end
152
- begin
153
- rows, columns = address_tool.as_integer_ranges(dest_address)
154
- dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
155
- dest_range_address = if (not dest_address_is_position)
156
- [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)
157
211
  else
158
- if (not options[:transpose])
159
- [rows.min..rows.min+self.Rows.Count-1, columns.min..columns.min+self.Columns.Count-1]
160
- else
161
- [rows.min..rows.min+self.Columns.Count-1, columns.min..columns.min+self.Rows.Count-1]
162
- end
163
- end
164
- dest_range = dest_sheet.range(dest_range_address)
165
- if options[:values_only]
166
- 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}
167
220
  else
168
- if dest_range.worksheet.workbook.excel == @worksheet.workbook.excel
169
- if options[:transpose]
170
- self.Copy
171
- dest_range.PasteSpecial(XlPasteAll,XlPasteSpecialOperationNone,false,true)
172
- else
173
- self.Copy(dest_range.ole_range)
174
- end
175
- else
176
- if options[:transpose]
177
- added_sheet = @worksheet.workbook.add_sheet
178
- self.copy(dest_address, added_sheet, :transpose => true)
179
- added_sheet.range(dest_range_address).copy(dest_address,dest_sheet)
180
- @worksheet.workbook.excel.with_displayalerts(false) {added_sheet.Delete}
181
- else
182
- self.Copy
183
- dest_sheet.Paste(dest_range.ole_range)
184
- end
185
- end
221
+ self.Copy
222
+ dest_sheet.Paste(dest_range.ole_range)
186
223
  end
187
- dest_range
188
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException => msg
189
- raise RangeNotCopied, 'cannot copy range'
190
224
  end
191
225
  end
226
+
227
+ public
192
228
 
193
229
  def == other_range
194
230
  other_range.is_a?(Range) &&
@@ -213,7 +249,7 @@ module RobustExcelOle
213
249
 
214
250
  # @private
215
251
  def to_s
216
- "#<REO::Range: " + "#{@ole_range.Address('External' => true).gsub(/\$/,'')} " + ">"
252
+ "#<REO::Range: #{@ole_range.Address(External: true).gsub(/\$/,'')} >"
217
253
  end
218
254
 
219
255
  # @private
@@ -221,11 +257,14 @@ module RobustExcelOle
221
257
  to_s
222
258
  end
223
259
 
260
+ using ParentRefinement
261
+ using StringRefinement
262
+
224
263
  # @private
225
264
  def self.worksheet_class
226
265
  @worksheet_class ||= begin
227
266
  module_name = parent_name
228
- "#{module_name}::Worksheet".constantize
267
+ "#{module_name}::Worksheet".constantize
229
268
  rescue NameError => e
230
269
  Worksheet
231
270
  end
@@ -241,22 +280,19 @@ module RobustExcelOle
241
280
  private
242
281
 
243
282
  def method_missing(name, *args)
244
- if name.to_s[0,1] =~ /[A-Z]/
245
- if ::ERRORMESSAGE_JRUBY_BUG
246
- begin
247
- @ole_range.send(name, *args)
248
- rescue Java::OrgRacobCom::ComFailException
249
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
250
- end
251
- else
252
- begin
253
- @ole_range.send(name, *args)
254
- rescue NoMethodError
255
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
256
- 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}"
257
289
  end
258
290
  else
259
- super
291
+ begin
292
+ @ole_range.send(name, *args)
293
+ rescue NoMethodError
294
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
295
+ end
260
296
  end
261
297
  end
262
298
  end