robust_excel_ole 1.35 → 1.36

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.
@@ -1,12 +1,59 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  require 'weakref'
4
- require 'Win32API'
4
+ require 'fiddle/import'
5
5
 
6
6
  def ka
7
7
  Excel.kill_all
8
8
  end
9
9
 
10
+ module User32
11
+ # Extend this module to an importer
12
+ extend Fiddle::Importer
13
+ # Load 'user32' dynamic library into this importer
14
+ dlload 'user32'
15
+ # Set C aliases to this importer for further understanding of function signatures
16
+ typealias 'HWND', 'HANDLE'
17
+ typealias 'HANDLE', 'void*'
18
+ typealias 'LPCSTR', 'const char*'
19
+ typealias 'LPCWSTR', 'const wchar_t*'
20
+ typealias 'UINT', 'unsigned int'
21
+ typealias 'HANDLE', 'void*'
22
+ typealias 'ppvObject', 'void**'
23
+ typealias 'DWORD', 'unsigned long'
24
+ typealias 'LPDWORD', 'DWORD*'
25
+ # Import C functions from loaded libraries and set them as module functions
26
+ extern 'DWORD GetWindowThreadProcessId(HWND, LPDWORD)'
27
+ extern 'HWND FindWindowExA(HWND, HWND, LPCSTR, LPCSTR)'
28
+ extern 'DWORD SetForegroundWindow(HWND)'
29
+ end
30
+
31
+ module Oleacc
32
+ # Extend this module to an importer
33
+ extend Fiddle::Importer
34
+ # Load 'oleacc' dynamic library into this importer
35
+ dlload 'oleacc'
36
+ # Set C aliases to this importer for further understanding of function signatures
37
+ typealias 'HWND', 'HANDLE'
38
+ typealias 'HANDLE', 'void*'
39
+ typealias 'ppvObject', 'void**'
40
+ typealias 'DWORD', 'unsigned long'
41
+ typealias 'HRESULT', 'long'
42
+ Guid = struct [
43
+ 'unsigned long data1',
44
+ 'unsigned short data2',
45
+ 'unsigned short data3',
46
+ 'unsigned char data4[8]'
47
+ ]
48
+ # Import C functions from loaded libraries and set them as module functions
49
+ extern 'HRESULT AccessibleObjectFromWindow(HWND, DWORD, struct guid*, ppvObject)'
50
+ #typealias 'REFIID', 'struct guid*'
51
+ #extern 'HRESULT AccessibleObjectFromWindow(HWND, DWORD, REFIID, ppvObject)'
52
+ #extern 'HRESULT AccessibleObjectFromWindow(HWND, DWORD, struct GUID*, ppvObject)'
53
+ #extern 'HRESULT AccessibleObjectFromWindow(HWND, DWORD, void*, ppvObject)'
54
+ #extern 'HRESULT AccessibleObjectFromWindow(HWND, DWORD, struct GUID*, ppvObject)'
55
+ end
56
+
10
57
  module RobustExcelOle
11
58
 
12
59
  # This class essentially wraps a Win32Ole Application object.
@@ -114,7 +161,7 @@ module RobustExcelOle
114
161
  @ole_excel = WIN32OLE.new('Excel.Application')
115
162
  set_options(opts)
116
163
  if opts[:reopen_workbooks]
117
- workbook_class.books.each{ |book| book.reopen if !book.alive? && book.excel.alive? && book.excel == self }
164
+ workbook_class.books.each{ |book| book.open if !book.alive? && book.excel.alive? && book.excel == self }
118
165
  end
119
166
  end
120
167
  self
@@ -294,9 +341,8 @@ module RobustExcelOle
294
341
  sleep 0.1
295
342
  if finishing_living_excel
296
343
  if hwnd
297
- process_id = Win32API.new('user32', 'GetWindowThreadProcessId', %w[I P], 'I')
298
344
  pid_puffer = ' ' * 32
299
- process_id.call(hwnd, pid_puffer)
345
+ User32::GetWindowThreadProcessId(hwnd, pid_puffer)
300
346
  pid = pid_puffer.unpack('L')[0]
301
347
  Process.kill('KILL', pid) rescue nil
302
348
  end
@@ -354,10 +400,50 @@ module RobustExcelOle
354
400
  WIN32OLE.connect('winmgmts:\\\\.').InstancesOf('win32_process').select { |p| p.Name == 'EXCEL.EXE' }.size
355
401
  end
356
402
 
357
- def self.known_instance_count
403
+ def self.known_instances_count
358
404
  @@hwnd2excel.size
359
405
  end
360
406
 
407
+ # returns running Excel instances
408
+ # !!! This is work in progress
409
+ # the approach is currently restricted to visible Excel instances with at least one workbook
410
+ def self.running_excel_instances
411
+ win32ole_excel_instances = []
412
+ hwnd = 0
413
+ loop do
414
+ hwnd = User32::FindWindowExA(0, hwnd, "XLMAIN", nil).to_i
415
+ break if hwnd == 0
416
+ hwnd2 = User32::FindWindowExA(hwnd, 0, "XLDESK", nil).to_i
417
+ hwnd3 = User32::FindWindowExA(hwnd2, 0, "EXCEL7", nil).to_i
418
+ interface_address_buffer = ' ' * 8
419
+ guid = Oleacc::Guid.malloc
420
+ guid.data1 = 0x20400
421
+ guid.data2 = 0x0
422
+ guid.data3 = 0x0
423
+ guid.data4 = [0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x46]
424
+ status = Oleacc::AccessibleObjectFromWindow(hwnd3, 0xFFFFFFF0, guid, interface_address_buffer)
425
+ interface_address = nil
426
+ if status == 0
427
+ interface_address = interface_address_buffer.unpack('L')[0]
428
+ else
429
+ raise ExcelREOError, "could not determine the addresss of the specified interface of the Excel object"
430
+ end
431
+ accessed_object_buffer = ' ' * 8
432
+ # open issue: is there a dll containing QueryInterface?
433
+ status = Ole32::QueryInterface(interface_address, guid, accessed_object_buffer)
434
+ if status == 0
435
+ accessed_object = accessed_object_buffer.unpack('L')[0]
436
+ # open issue: a method, similar to create_win32ole in Win32ole creating a win32ole object
437
+ # we could use pr-win32ole (seems to be an old ruby gem, needing C to be installed)
438
+ ole_excel = create_win32ole(accessed_object)
439
+ win32ole_excel_instances << ole_excel.Application
440
+ else
441
+ raise ExcelREOError, "could not determine the Excel object from window"
442
+ end
443
+ end
444
+ win32ole_excel_instances.map{|w| w.to_reo}
445
+ end
446
+
361
447
  # returns a running Excel instance opened with RobustExcelOle
362
448
  def self.known_running_instance
363
449
  self.known_running_instances.first
@@ -369,9 +455,8 @@ module RobustExcelOle
369
455
  @@hwnd2excel.each do |hwnd,wr_excel|
370
456
  next unless wr_excel.weakref_alive?
371
457
  excel = wr_excel.__getobj__
372
- process_id = Win32API.new('user32', 'GetWindowThreadProcessId', %w[I P], 'I')
373
458
  pid_puffer = ' ' * 32
374
- process_id.call(hwnd, pid_puffer)
459
+ User32::GetWindowThreadProcessId(hwnd, pid_puffer)
375
460
  pid = pid_puffer.unpack('L')[0]
376
461
  pid2excel[pid] = excel
377
462
  end
@@ -380,10 +465,10 @@ module RobustExcelOle
380
465
  end
381
466
 
382
467
  class << self
383
- alias excels_number instance_count # :deprecated :#
384
- alias known_excels_number known_instance_count # :deprecated :#
385
- alias known_excel_instance known_running_instance # :deprecated :#
386
- alias known_excel_instances known_running_instances # :deprecated :#
468
+ alias excels_number instance_count # :deprecated: #
469
+ alias known_excels_number known_instances_count # :deprecated: #
470
+ alias known_excel_instance known_running_instance # :deprecated: #
471
+ alias known_excel_instances known_running_instances # :deprecated: #
387
472
  end
388
473
 
389
474
  private
@@ -627,11 +712,8 @@ module RobustExcelOle
627
712
 
628
713
  def focus
629
714
  self.visible = true
630
- # if not Windows10 then
631
- Win32API.new('user32','SetForegroundWindow','I','I').call(@ole_excel.Hwnd)
632
- # else
633
- # Win32API.new("user32","SetForegroundWindow","","I").call
634
- # end
715
+ status = User32::SetForegroundWindow(@ole_excel.Hwnd)
716
+ raise ExcelREOError, "could not set Excel window as foreground" if status == 0
635
717
  end
636
718
 
637
719
  # @private
@@ -224,18 +224,30 @@ module General
224
224
  network = WIN32OLE.new('WScript.Network')
225
225
  drives = network.enumnetworkdrives
226
226
  count = drives.Count
227
- (0..(count - 1)).step(2).map{ |i| NetworkDrive.new( drives.Item(i), drives.Item(i + 1).tr('\\','/')) }
227
+ # (0..(count - 1)).step(2).map{ |i| NetworkDrive.new( drives.Item(i), drives.Item(i + 1).tr('\\','/')) }
228
+ result = (0..(count - 1)).step(2).map { |i|
229
+ NetworkDrive.new( drives.Item(i), drives.Item(i + 1).tr('\\','/')) unless drives.Item(i).empty?
230
+ }.compact
231
+ result
228
232
  end
229
233
  end
230
234
 
231
235
  # @private
232
236
  def hostnameshare2networkpath(filename)
233
237
  return filename unless filename[0,2] == "//"
234
- NetworkDrive.get_all_drives.inject(filename) do |fn_modified, d|
235
- fn_modified.sub(/#{(Regexp.escape(d.network_name))}/i,d.drive_letter)
236
- end
237
- end
238
-
238
+ hostname = filename[0,filename[3,filename.length].index('/')+3]
239
+ filename_wo_hostname = filename[hostname.length+1,filename.length]
240
+ abs_filename = absolute_path(filename_wo_hostname).tr('\\','/').tr('C:/','c$/')
241
+ adapted_filename = hostname + "/" + abs_filename
242
+ NetworkDrive.get_all_drives.each do |d|
243
+ new_filename = filename.sub(/#{(Regexp.escape(d.network_name))}/i,d.drive_letter)
244
+ return new_filename if new_filename != filename
245
+ new_filename = adapted_filename.sub(/#{(Regexp.escape(d.network_name))}/i,d.drive_letter)
246
+ return new_filename if new_filename != filename
247
+ end
248
+ filename
249
+ end
250
+
239
251
  # @private
240
252
  def absolute_path(file)
241
253
  file = file.to_path if file.respond_to?(:to_path)
@@ -60,23 +60,6 @@ module RobustExcelOle
60
60
  end
61
61
  end
62
62
 
63
- =begin
64
-
65
- ole_table = @ole_table
66
-
67
- @row_class = Class.new(ListRow) do
68
-
69
- @@ole_table = ole_table
70
-
71
- def ole_table
72
- @@ole_table
73
- end
74
-
75
- end
76
-
77
- end
78
-
79
- =end
80
63
 
81
64
  ole_table = @ole_table
82
65
 
@@ -141,13 +124,18 @@ module RobustExcelOle
141
124
 
142
125
  private
143
126
 
144
- def matching_via_traversing(key_hash, opts)
127
+ def matching_via_traversing(key_hash, opts)
145
128
  encode_utf8 = ->(val) {val.respond_to?(:gsub) ? val.encode('utf-8') : val}
146
129
  cn2i = column_names_to_index
147
130
  max_matching_num = opts[:limit] || 65536
148
131
  matching_rows = @ole_table.ListRows.lazy.select { |listrow|
149
132
  rowvalues = listrow.Range.Value.first
150
- key_hash.all?{ |key,val| encode_utf8.(rowvalues[cn2i[key]])==val}
133
+ key_hash.all?{|key,val|
134
+ rowvalue = encode_utf8.(rowvalues[cn2i[key]])
135
+ rowvalue == val ||
136
+ (val == "0" && rowvalue == 0) ||
137
+ (rowvalue.respond_to?(:abs) && val.to_i != 0 && rowvalue == val.to_i)
138
+ }
151
139
  }.take(max_matching_num).to_a
152
140
  rescue
153
141
  raise(TableError, "cannot find row with key #{key_hash}")
@@ -386,6 +374,10 @@ module RobustExcelOle
386
374
  self.Parent.to_reo == other_table.Parent.to_reo
387
375
  end
388
376
 
377
+ # @private
378
+ def workbook
379
+ @workbook ||= ole_table.Parent.Parent.to_reo
380
+ end
389
381
 
390
382
  # @private
391
383
  # returns true, if the list object responds to VBA methods, false otherwise
@@ -27,6 +27,7 @@ module RobustExcelOle
27
27
  # @param [Variant] column number or column name
28
28
  # @return [Variant] value of the cell
29
29
  def [] column_number_or_name
30
+ column_number_or_name = column_number_or_name.to_s if column_number_or_name.is_a?(Symbol)
30
31
  ole_cell = ole_table.Application.Intersect(
31
32
  @ole_tablerow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
32
33
  value = ole_cell.Value
@@ -40,6 +41,7 @@ module RobustExcelOle
40
41
  # @param [Variant] value of the cell
41
42
  def []=(column_number_or_name, value)
42
43
  begin
44
+ column_number_or_name = column_number_or_name.to_s if column_number_or_name.is_a?(Symbol)
43
45
  ole_cell = ole_table.Application.Intersect(
44
46
  @ole_tablerow.Range, ole_table.ListColumns.Item(column_number_or_name).Range)
45
47
  ole_cell.Value = value
@@ -96,27 +98,68 @@ module RobustExcelOle
96
98
  other_listrow.is_a?(ListRow) && other_listrow.values == self.values
97
99
  end
98
100
 
99
- def method_missing(name, *args)
100
- # this should not happen:
101
- raise(TableRowError, "internal error: ole_table not defined") unless self.class.method_defined?(:ole_table)
102
- name_str = name.to_s
103
- core_name = name_str.chomp('=')
104
- column_names = ole_table.HeaderRowRange.Value.first
105
- column_name = column_names.find do |c|
106
- c == core_name ||
107
- c.gsub(/\W/,'_') == core_name ||
108
- c.underscore == core_name ||
109
- c.underscore.gsub(/\W/,'_') == core_name ||
110
- c.replace_umlauts.gsub(/\W/,'_') == core_name ||
111
- c.replace_umlauts.underscore.gsub(/\W/,'_') == core_name
112
- end
113
- if column_name
114
- define_and_call_method(column_name, name, *args)
115
- else
116
- super(name, *args)
101
+ # @private
102
+ def workbook
103
+ @workbook ||= workbook_class.new(ole_table.Parent.Parent)
104
+ end
105
+
106
+ # @private
107
+ def self.workbook_class
108
+ @workbook_class ||= begin
109
+ module_name = parent_name
110
+ "#{module_name}::Workbook".constantize
111
+ rescue NameError => e
112
+ Workbook
117
113
  end
118
114
  end
119
115
 
116
+ # @private
117
+ def workbook_class
118
+ self.class.workbook_class
119
+ end
120
+
121
+ # @private
122
+ def column_names
123
+ ole_table.HeaderRowRange.Value.first
124
+ end
125
+
126
+ # returns true, if the listrow reacts to methods, false otherwise
127
+ def alive?
128
+ @ole_tablerow.Parent
129
+ true
130
+ rescue
131
+ @ole_tablerow = nil # dead object won't be alive again
132
+ false
133
+ end
134
+
135
+ private
136
+
137
+ def valid_similar_names meth_name
138
+ [
139
+ meth_name,
140
+ meth_name.gsub(/\W/,'_'),
141
+ meth_name.underscore,
142
+ meth_name.underscore.gsub(/\W/,'_'),
143
+ meth_name.replace_umlauts.gsub(/\W/,'_'),
144
+ meth_name.replace_umlauts.underscore.gsub(/\W/,'_')
145
+ ].uniq
146
+ end
147
+
148
+ public
149
+
150
+ # @private
151
+ def methods
152
+ @methods ||= begin
153
+ arr = column_names.map{ |c| valid_similar_names(c) }.flatten
154
+ (arr + arr.map{|m| m + '='}).map(&:to_sym) + super
155
+ end
156
+ end
157
+
158
+ # @private
159
+ def respond_to?(meth_name)
160
+ methods.include?(meth_name.to_sym)
161
+ end
162
+
120
163
  # @private
121
164
  def to_s
122
165
  inspect
@@ -129,7 +172,19 @@ module RobustExcelOle
129
172
 
130
173
  private
131
174
 
132
- def define_and_call_method(column_name,method_name,*args)
175
+ def method_missing(meth_name, *args)
176
+ # this should not happen:
177
+ raise(TableRowError, "internal error: ole_table not defined") unless self.class.method_defined?(:ole_table)
178
+ if respond_to?(meth_name)
179
+ core_name = meth_name.to_s.chomp('=')
180
+ column_name = column_names.find{ |c| valid_similar_names(c).include?(core_name) }
181
+ define_and_call_method(column_name, meth_name, *args) if column_name
182
+ else
183
+ super(meth_name, *args)
184
+ end
185
+ end
186
+
187
+ def define_and_call_method(column_name, method_name, *args)
133
188
  #column_name = column_name.force_encoding('cp850')
134
189
  ole_cell = ole_table.Application.Intersect(
135
190
  @ole_tablerow.Range, ole_table.ListColumns.Item(column_name).Range)
@@ -223,6 +223,11 @@ module RobustExcelOle
223
223
  @worksheet.workbook.excel
224
224
  end
225
225
 
226
+ # @private
227
+ def workbook
228
+ @workbook ||= @worksheet.workbook
229
+ end
230
+
226
231
  # @private
227
232
  # returns true, if the Range object responds to VBA methods, false otherwise
228
233
  def alive?
@@ -231,7 +236,7 @@ module RobustExcelOle
231
236
  rescue
232
237
  # trace $!.message
233
238
  false
234
- end
239
+ end
235
240
 
236
241
  # @private
237
242
  def to_s
@@ -15,22 +15,25 @@ module RobustExcelOle
15
15
  # @option opts [Symbol] :default the default value that is provided if no contents could be returned
16
16
  # @return [Variant] the contents of a range with given name
17
17
  def namevalue_global(name, opts = { default: :__not_provided })
18
- name_obj = begin
19
- get_name_object(name)
20
- rescue NameNotFound => msg
21
- raise
22
- end
23
- ole_range = name_obj.RefersToRange
24
- worksheet = self if self.is_a?(Worksheet)
25
- value = begin
26
- if !::RANGES_JRUBY_BUG
27
- ole_range.Value
28
- else
29
- values = RobustExcelOle::Range.new(ole_range, worksheet).v
30
- (values.size==1 && values.first.size==1) ? values.first.first : values
18
+ begin
19
+ name_obj = begin
20
+ get_name_object(name)
21
+ rescue NameNotFound
22
+ raise
31
23
  end
32
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
24
+ ole_range = name_obj.RefersToRange
25
+ worksheet = self if self.is_a?(Worksheet)
26
+ value = begin
27
+ if !::RANGES_JRUBY_BUG
28
+ ole_range.Value
29
+ else
30
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
31
+ (values.size==1 && values.first.size==1) ? values.first.first : values
32
+ end
33
+ end
34
+ rescue # WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
33
35
  sheet = if self.is_a?(Worksheet) then self
36
+ # chooses simply the 1st worksheet?
34
37
  elsif self.is_a?(Workbook) then self.sheet(1)
35
38
  end
36
39
  begin
@@ -42,16 +45,20 @@ module RobustExcelOle
42
45
  values = RobustExcelOle::Range.new(ole_range, worksheet).v
43
46
  (values.size==1 && values.first.size==1) ? values.first.first : values
44
47
  end
45
- rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
48
+ rescue # WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
46
49
  return opts[:default] unless opts[:default] == :__not_provided
47
- raise RangeNotEvaluatable, "cannot evaluate range named #{name.inspect} in #{self}"
50
+ if name_obj.nil?
51
+ raise NameNotFound, "cannot find name #{name.inspect}"
52
+ else
53
+ raise RangeNotEvaluatable, "cannot evaluate range named #{name.inspect}"
54
+ end
48
55
  end
49
56
  end
50
57
  if value == -2146828288 + RobustExcelOle::XlErrName
51
58
  return opts[:default] unless opts[:default] == :__not_provided
52
59
  raise RangeNotEvaluatable, "cannot evaluate range named #{name.inspect} in #{File.basename(workbook.stored_filename).inspect rescue nil}"
53
60
  end
54
- return opts[:default] unless (opts[:default] == :__not_provided) || value.nil?
61
+ return opts[:default] if opts[:default] != :__not_provided && !value.nil?
55
62
  value
56
63
  end
57
64
 
@@ -1,3 +1,3 @@
1
1
  module RobustExcelOle
2
- VERSION = "1.35"
2
+ VERSION = "1.36"
3
3
  end