robust_excel_ole 1.19.10 → 1.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,8 @@ module RobustExcelOle
14
14
 
15
15
  attr_reader :ole_table
16
16
 
17
+ alias ole_object ole_table
18
+
17
19
  # constructs a list object (or table).
18
20
  # @param [Variable] worksheet_or_ole_listobject a worksheet or a Win32Ole list object
19
21
  # @param [Variable] table_name_or_number a table name or table number
@@ -58,31 +60,99 @@ module RobustExcelOle
58
60
  def initialize(row_number)
59
61
  @ole_listrow = @@ole_table.ListRows.Item(row_number)
60
62
  end
61
-
63
+
64
+ # returns the value of the cell with given column name or number
65
+ # @param [Variant] column number or column name
66
+ # @return [Variant] value of the cell
67
+ def [] column_number_or_name
68
+ begin
69
+ ole_cell = @@ole_table.Application.Intersect(
70
+ @ole_listrow.Range, @@ole_table.ListColumns.Item(column_number_or_name).Range)
71
+ ole_cell.Value
72
+ rescue WIN32OLERuntimeError
73
+ raise TableRowError, "could not determine the value at column #{column_number_or_name}"
74
+ end
75
+ end
76
+
77
+
78
+ def []=(column_number_or_name, value)
79
+ begin
80
+ ole_cell = @@ole_table.Application.Intersect(
81
+ @ole_listrow.Range, @@ole_table.ListColumns.Item(column_number_or_name).Range)
82
+ ole_cell.Value = value
83
+ rescue WIN32OLERuntimeError
84
+ raise TableRowError, "could not assign value #{value.inspect} to cell at column #{column_number_or_name}"
85
+ end
86
+ end
87
+
88
+ # values of the row
89
+ # @return [Array] values of the row
90
+ def values
91
+ begin
92
+ @ole_listrow.Range.Value.first
93
+ rescue WIN32OLERuntimeError
94
+ raise TableError, "could not read values"
95
+ end
96
+ end
97
+
98
+ # sets the values of the row
99
+ # @param [Array] values of the row
100
+ def set_values values
101
+ begin
102
+ updated_values = self.values
103
+ updated_values[0,values.length] = values
104
+ @ole_listrow.Range.Value = [updated_values]
105
+ values
106
+ rescue WIN32OLERuntimeError
107
+ raise TableError, "could not set values #{values.inspect}"
108
+ end
109
+ end
110
+
111
+ # deletes the values of the row
112
+ def delete_values
113
+ begin
114
+ @ole_listrow.Range.Value = [[].fill(nil,0..(@@ole_table.ListColumns.Count)-1)]
115
+ nil
116
+ rescue WIN32OLERuntimeError
117
+ raise TableError, "could not delete values"
118
+ end
119
+ end
120
+
62
121
  def method_missing(name, *args)
63
- name_before_last_equal = name.to_s.split('=').first
122
+ name_str = name.to_s
123
+ core_name = name_str[-1]!='=' ? name_str : name_str[0..-2]
64
124
  column_names = @@ole_table.HeaderRowRange.Value.first
65
- method_names = column_names.map{|c| c.underscore.gsub(/[^[\w\d]]/, '_')}
66
- column_name = column_names[method_names.index(name_before_last_equal)]
125
+ column_name = column_names.find do |c|
126
+ c == core_name ||
127
+ c.gsub(/\W/,'') == core_name ||
128
+ c.replace_umlauts == core_name ||
129
+ c.gsub(/\W/,'').replace_umlauts == core_name ||
130
+ c.gsub(/\W/,'').replace_umlauts.underscore.gsub(/[^[\w\d]]/, '_').delete_multiple_underscores == core_name
131
+ end
67
132
  if column_name
68
- ole_cell = @@ole_table.Application.Intersect(
69
- @ole_listrow.Range, @@ole_table.ListColumns(column_name).Range)
70
- define_getting_setting_method(ole_cell,name.to_s)
71
- self.send(name, *args)
133
+ method_name = core_name.gsub(/\W/,'') + (name_str[-1]!='=' ? "" : "=")
134
+ define_and_call_method(column_name,method_name,*args)
72
135
  else
73
- super
136
+ super(name, *args)
74
137
  end
75
138
  end
76
139
 
77
140
  private
78
141
 
79
- def define_getting_setting_method(ole_cell,name_str)
80
- if name_str[-1] != '='
81
- self.class.define_method(name_str) do
142
+ def define_and_call_method(column_name,method_name,*args)
143
+ ole_cell = @@ole_table.Application.Intersect(
144
+ @ole_listrow.Range, @@ole_table.ListColumns.Item(column_name).Range)
145
+ define_getting_setting_method(ole_cell,method_name)
146
+ self.send(method_name, *args)
147
+ end
148
+
149
+ def define_getting_setting_method(ole_cell,name)
150
+ if name[-1] != '='
151
+ self.class.define_method(name) do
82
152
  ole_cell.Value
83
153
  end
84
154
  else
85
- self.class.define_method(name_str) do |value|
155
+ self.class.define_method(name) do |value|
86
156
  ole_cell.Value = value
87
157
  end
88
158
  end
@@ -98,8 +168,208 @@ module RobustExcelOle
98
168
 
99
169
  end
100
170
 
171
+ # @return [Array] a list of column names
101
172
  def column_names
102
- @ole_table.HeaderRowRange.Value.first
173
+ begin
174
+ @ole_table.HeaderRowRange.Value.first
175
+ rescue WIN32OLERuntimeError
176
+ raise TableError, "could not determine column names"
177
+ end
178
+ end
179
+
180
+ # adds a row
181
+ # @param [Integer] position of the new row
182
+ # @param [Array] values of the column
183
+ def add_row(position = nil, contents = nil)
184
+ begin
185
+ @ole_table.ListRows.Add(position)
186
+ set_row_values(position, contents) if contents
187
+ rescue WIN32OLERuntimeError
188
+ raise TableError, ("could not add row" + (" at position #{position.inspect}" if position))
189
+ end
190
+ end
191
+
192
+ # adds a column
193
+ # @param [String] name of the column
194
+ # @param [Integer] position of the new column
195
+ # @param [Array] values of the column
196
+ def add_column(column_name = nil, position = nil, contents = nil)
197
+ begin
198
+ new_column = @ole_table.ListColumns.Add(position)
199
+ new_column.Name = column_name if column_name
200
+ set_column_values(column_name, contents) if contents
201
+ rescue WIN32OLERuntimeError, TableError
202
+ raise TableError, ("could not add column"+ ("at position #{position.inspect} with name #{column_name.inspect}" if position))
203
+ end
204
+ end
205
+
206
+ # deletes a row
207
+ # @param [Integer] position of the old row
208
+ def delete_row(row_number)
209
+ begin
210
+ @ole_table.ListRows.Item(row_number).Delete
211
+ rescue WIN32OLERuntimeError
212
+ raise TableError, "could not delete row #{row_number.inspect}"
213
+ end
214
+ end
215
+
216
+ # deletes a column
217
+ # @param [Variant] column number or column name
218
+ def delete_column(column_number_or_name)
219
+ begin
220
+ @ole_table.ListColumns.Item(column_number_or_name).Delete
221
+ rescue WIN32OLERuntimeError
222
+ raise TableError, "could not delete column #{column_number_or_name.inspect}"
223
+ end
224
+ end
225
+
226
+ # deletes the contents of a row
227
+ # @param [Integer] row number
228
+ def delete_row_values(row_number)
229
+ begin
230
+ @ole_table.ListRows.Item(row_number).Range.Value = [[].fill(nil,0..(@ole_table.ListColumns.Count-1))]
231
+ nil
232
+ rescue WIN32OLERuntimeError
233
+ raise TableError, "could not delete contents of row #{row_number.inspect}"
234
+ end
235
+ end
236
+
237
+ # deletes the contents of a column
238
+ # @param [Variant] column number or column name
239
+ def delete_column_values(column_number_or_name)
240
+ begin
241
+ column_name = @ole_table.ListColumns.Item(column_number_or_name).Range.Value.first
242
+ @ole_table.ListColumns.Item(column_number_or_name).Range.Value = [column_name] + [].fill([nil],0..(@ole_table.ListRows.Count-1))
243
+ nil
244
+ rescue WIN32OLERuntimeError
245
+ raise TableError, "could not delete contents of column #{column_number_or_name.inspect}"
246
+ end
247
+ end
248
+
249
+ # renames a row
250
+ # @param [String] previous name or number of the column
251
+ # @param [String] new name of the column
252
+ def rename_column(name_or_number, new_name)
253
+ begin
254
+ @ole_table.ListColumns.Item(name_or_number).Name = new_name
255
+ rescue
256
+ raise TableError, "could not rename column #{name_or_number.inspect} to #{new_name.inspect}"
257
+ end
258
+ end
259
+
260
+ # contents of a row
261
+ # @param [Integer] row number
262
+ # @return [Array] contents of a row
263
+ def row_values(row_number)
264
+ begin
265
+ @ole_table.ListRows.Item(row_number).Range.Value.first
266
+ rescue WIN32OLERuntimeError
267
+ raise TableError, "could not read the values of row #{row_number.inspect}"
268
+ end
269
+ end
270
+
271
+ # sets the contents of a row
272
+ # @param [Integer] row number
273
+ # @param [Array] values of the row
274
+ def set_row_values(row_number, values)
275
+ begin
276
+ updated_values = row_values(row_number)
277
+ updated_values[0,values.length] = values
278
+ @ole_table.ListRows.Item(row_number).Range.Value = [updated_values]
279
+ values
280
+ rescue WIN32OLERuntimeError
281
+ raise TableError, "could not set the values of row #{row_number.inspect}"
282
+ end
283
+ end
284
+
285
+ # @return [Array] contents of a column
286
+ def column_values(column_number_or_name)
287
+ begin
288
+ @ole_table.ListColumns.Item(column_number_or_name).Range.Value[1,@ole_table.ListRows.Count].flatten
289
+ rescue WIN32OLERuntimeError
290
+ raise TableError, "could not read the values of column #{column_number_or_name.inspect}"
291
+ end
292
+ end
293
+
294
+ # sets the contents of a column
295
+ # @param [Integer] column name or column number
296
+ # @param [Array] contents of the column
297
+ def set_column_values(column_number_or_name, values)
298
+ begin
299
+ updated_values = column_values(column_number_or_name)
300
+ updated_values[0,values.length] = values
301
+ column_name = @ole_table.ListColumns.Item(column_number_or_name).Range.Value.first
302
+ @ole_table.ListColumns.Item(column_number_or_name).Range.Value = column_name + updated_values.map{|v| [v]}
303
+ values
304
+ rescue WIN32OLERuntimeError
305
+ raise TableError, "could not read the values of column #{column_number_or_name.inspect}"
306
+ end
307
+ end
308
+
309
+ # deletes rows that have an empty contents
310
+ def delete_empty_rows
311
+ listrows = @ole_table.ListRows
312
+ nil_array = [[].fill(nil,0..(@ole_table.ListColumns.Count-1))]
313
+ i = 1
314
+ while i <= listrows.Count do
315
+ row = listrows.Item(i)
316
+ if row.Range.Value == nil_array
317
+ row.Delete
318
+ else
319
+ i = i+1
320
+ end
321
+ end
322
+ end
323
+
324
+ # deletes columns that have an empty contents
325
+ def delete_empty_columns
326
+ listcolumns = @ole_table.ListColumns
327
+ nil_array = [].fill([nil],0..(@ole_table.ListRows.Count-1))
328
+ i = 1
329
+ while i <= listcolumns.Count do
330
+ column = listcolumns.Item(i)
331
+ if column.Range.Value[1..-1] == nil_array
332
+ column.Delete
333
+ else
334
+ i = i+1
335
+ end
336
+ end
337
+ end
338
+
339
+ # finds all cells containing a given value
340
+ # @param[Variant] value to find
341
+ # @return [Array] win32ole cells containing the given value
342
+ def find_cells(value)
343
+ listrows = @ole_table.ListRows
344
+ result = []
345
+ (1..listrows.Count).each do |row_number|
346
+ row_values(row_number).find_each_index(value).each do |col_number|
347
+ result << @ole_table.Application.Intersect(listrows.Item(row_number).Range,
348
+ @ole_table.ListColumns.Item(col_number+1).Range).to_reo
349
+ end
350
+ end
351
+ result
352
+ end
353
+
354
+ # sorts the rows of the list object according to the given column
355
+ # @param [Variant] column number or name
356
+ # @option opts [Symbol] sort order
357
+ def sort(column_number_or_name, sort_order = :ascending)
358
+ key_range = @ole_table.ListColumns.Item(column_number_or_name).Range
359
+ @ole_table.Sort.SortFields.Clear
360
+ sort_order_option = sort_order == :ascending ? XlAscending : XlDescending
361
+ @ole_table.Sort.SortFields.Add(key_range, XlSortOnValues,sort_order_option,XlSortNormal)
362
+ @ole_table.Sort.Apply
363
+ end
364
+
365
+ # @private
366
+ # returns true, if the list object responds to VBA methods, false otherwise
367
+ def alive?
368
+ @ole_table.ListRows
369
+ true
370
+ rescue
371
+ # trace $!.message
372
+ false
103
373
  end
104
374
 
105
375
  # @private
@@ -110,10 +380,12 @@ module RobustExcelOle
110
380
  # @private
111
381
  def inspect
112
382
  "#<ListObject:" + "#{@ole_table.Name}" +
113
- " size:#{@ole_table.ListRows.Count}x#{@ole_table.ListColumns.Count}" +
114
- " worksheet:#{@ole_table.Parent.Name}" + " workbook:#{@ole_table.Parent.Parent.Name}" + ">"
383
+ " #{@ole_table.ListRows.Count}x#{@ole_table.ListColumns.Count}" +
384
+ " #{@ole_table.Parent.Name}" + " #{@ole_table.Parent.Parent.Name}" + ">"
115
385
  end
116
386
 
387
+ include MethodHelpers
388
+
117
389
  private
118
390
 
119
391
  def method_missing(name, *args)
@@ -141,6 +413,10 @@ module RobustExcelOle
141
413
  class TableError < WorksheetREOError
142
414
  end
143
415
 
416
+ # @private
417
+ class TableRowError < WorksheetREOError
418
+ end
419
+
144
420
  Table = ListObject
145
421
  TableRow = ListRow
146
422
 
@@ -8,14 +8,20 @@ module RobustExcelOle
8
8
  # See https://docs.microsoft.com/en-us/office/vba/api/excel.worksheet#methods
9
9
 
10
10
  class Range < VbaObjects
11
+
11
12
  include Enumerable
13
+
12
14
  attr_reader :ole_range
13
15
  attr_reader :worksheet
14
16
 
17
+ alias ole_object ole_range
18
+
19
+
15
20
  def initialize(win32_range, worksheet = nil)
16
21
  @ole_range = win32_range
17
- @worksheet = worksheet ? worksheet : worksheet_class.new(self.Parent)
18
- #@worksheet = worksheet_class.new(self.Parent)
22
+ @worksheet = worksheet ? worksheet.to_reo : worksheet_class.new(self.Parent)
23
+ address_r1c1 = @ole_range.AddressLocal(true,true,XlR1C1)
24
+ @rows, @columns = address_tool.as_integer_ranges(address_r1c1)
19
25
  end
20
26
 
21
27
  def each
@@ -64,12 +70,10 @@ module RobustExcelOle
64
70
  if !::RANGES_JRUBY_BUG
65
71
  self.Value
66
72
  else
67
- address_r1c1 = self.AddressLocal(true,true,XlR1C1)
68
- row, col = address_tool.as_integer_ranges(address_r1c1)
69
73
  values = []
70
- row.each do |r|
74
+ @rows.each do |r|
71
75
  values_col = []
72
- col.each{ |c| values_col << worksheet.Cells(r,c).Value}
76
+ @columns.each{ |c| values_col << worksheet.Cells(r,c).Value}
73
77
  values << values_col
74
78
  end
75
79
  values
@@ -85,10 +89,8 @@ module RobustExcelOle
85
89
  if !::RANGES_JRUBY_BUG
86
90
  ole_range.Value = value
87
91
  else
88
- address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
89
- row, col = address_tool.as_integer_ranges(address_r1c1)
90
- row.each_with_index do |r,i|
91
- col.each_with_index do |c,j|
92
+ @rows.each_with_index do |r,i|
93
+ @columns.each_with_index do |c,j|
92
94
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
93
95
  end
94
96
  end
@@ -107,55 +109,52 @@ module RobustExcelOle
107
109
  # @options [Worksheet] the destination worksheet
108
110
  # @options [Hash] options: :transpose, :values_only
109
111
  def copy(dest_address1, sheet_or_dest_address2 = :__not_provided, options_or_sheet = :__not_provided, not_provided_or_options = :__not_provided)
110
- dest_address = if sheet_or_dest_address2.is_a?(Object::Range) or sheet_or_dest_address2.is_a?(Integer)
111
- [dest_address1,sheet_or_dest_address2]
112
- else
113
- dest_address1
114
- end
115
- dest_sheet = if sheet_or_dest_address2.is_a?(Worksheet)
116
- sheet_or_dest_address2
117
- else
118
- if options_or_sheet.is_a?(Worksheet)
119
- options_or_sheet
112
+ begin
113
+ dest_address = if sheet_or_dest_address2.is_a?(Object::Range) or sheet_or_dest_address2.is_a?(Integer)
114
+ [dest_address1,sheet_or_dest_address2]
120
115
  else
121
- @worksheet
116
+ dest_address1
122
117
  end
123
- end
124
- options = if options_or_sheet.is_a?(Hash)
125
- options_or_sheet
126
- else
127
- if not_provided_or_options.is_a?(Hash)
128
- not_provided_or_options
118
+ dest_sheet = if sheet_or_dest_address2.is_a?(Worksheet) or sheet_or_dest_address2.is_a?(WIN32OLE)
119
+ sheet_or_dest_address2.to_reo
129
120
  else
130
- { }
121
+ if options_or_sheet.is_a?(Worksheet) or options_or_sheet.is_a?(WIN32OLE)
122
+ options_or_sheet.to_reo
123
+ else
124
+ @worksheet
125
+ end
131
126
  end
132
- end
133
- rows, columns = address_tool.as_integer_ranges(dest_address)
134
- dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
135
- dest_range_address = if (not dest_address_is_position)
136
- [rows.min..rows.max,columns.min..columns.max]
127
+ options = if options_or_sheet.is_a?(Hash)
128
+ options_or_sheet
137
129
  else
138
- if (not options[:transpose])
139
- [rows.min..rows.min+self.Rows.Count-1,
140
- columns.min..columns.min+self.Columns.Count-1]
130
+ if not_provided_or_options.is_a?(Hash)
131
+ not_provided_or_options
141
132
  else
142
- [rows.min..rows.min+self.Columns.Count-1,
143
- columns.min..columns.min+self.Rows.Count-1]
133
+ { }
144
134
  end
145
135
  end
146
- dest_range = dest_sheet.range(dest_range_address)
147
- begin
136
+ rows, columns = address_tool.as_integer_ranges(dest_address)
137
+ dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
138
+ dest_range_address = if (not dest_address_is_position)
139
+ [rows.min..rows.max,columns.min..columns.max]
140
+ else
141
+ if (not options[:transpose])
142
+ [rows.min..rows.min+self.Rows.Count-1,
143
+ columns.min..columns.min+self.Columns.Count-1]
144
+ else
145
+ [rows.min..rows.min+self.Columns.Count-1,
146
+ columns.min..columns.min+self.Rows.Count-1]
147
+ end
148
+ end
149
+ dest_range = dest_sheet.range(dest_range_address)
148
150
  if options[:values_only]
149
- # dest_range.Value = options[:transpose] ? self.Value.transpose : self.Value
150
151
  dest_range.v = options[:transpose] ? self.v.transpose : self.v
151
152
  else
152
153
  if dest_range.worksheet.workbook.excel == @worksheet.workbook.excel
153
154
  if options[:transpose]
154
155
  self.Copy
155
- #dest_range.PasteSpecial('transpose' => true)
156
156
  dest_range.PasteSpecial(XlPasteAll,XlPasteSpecialOperationNone,false,true)
157
157
  else
158
- #self.Copy('destination' => dest_range.ole_range)
159
158
  self.Copy(dest_range.ole_range)
160
159
  end
161
160
  else
@@ -166,7 +165,6 @@ module RobustExcelOle
166
165
  @worksheet.workbook.excel.with_displayalerts(false) {added_sheet.Delete}
167
166
  else
168
167
  self.Copy
169
- #dest_sheet.Paste('destination' => dest_range.ole_range)
170
168
  dest_sheet.Paste(dest_range.ole_range)
171
169
  end
172
170
  end
@@ -184,7 +182,7 @@ module RobustExcelOle
184
182
  def copy_special(dest_address, dest_sheet = :__not_provided, options = { })
185
183
  rows, columns = address_tool.as_integer_ranges(dest_address)
186
184
  dest_sheet = @worksheet if dest_sheet == :__not_provided
187
- dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
185
+ dest_address_is_position = (rows.min == rows.max && @columns.min == @columns.max)
188
186
  dest_range_address = if (not dest_address_is_position)
189
187
  [rows.min..rows.max,columns.min..columns.max]
190
188
  else
@@ -239,6 +237,26 @@ module RobustExcelOle
239
237
  @worksheet.workbook.excel
240
238
  end
241
239
 
240
+ # @private
241
+ # returns true, if the Range object responds to VBA methods, false otherwise
242
+ def alive?
243
+ @ole_range.Row
244
+ true
245
+ rescue
246
+ # trace $!.message
247
+ false
248
+ end
249
+
250
+ # @private
251
+ def to_s
252
+ "#<REO::Range: " + "[#{@rows},#{@columns}] " + "#{worksheet.Name} " + ">"
253
+ end
254
+
255
+ # @private
256
+ def inspect
257
+ self.to_s
258
+ end
259
+
242
260
  # @private
243
261
  def self.worksheet_class
244
262
  @worksheet_class ||= begin
@@ -254,6 +272,8 @@ module RobustExcelOle
254
272
  self.class.worksheet_class
255
273
  end
256
274
 
275
+ include MethodHelpers
276
+
257
277
  private
258
278
 
259
279
  def method_missing(name, *args)