robust_excel_ole 1.19.10 → 1.21

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