robust_excel_ole 1.35 → 1.36

Sign up to get free protection for your applications and to get access to all the features.
@@ -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