robust_excel_ole 1.15 → 1.18.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +27 -0
  3. data/README.rdoc +18 -1
  4. data/___dummy_workbook.xls +0 -0
  5. data/docs/README_excel.rdoc +6 -0
  6. data/docs/README_open.rdoc +18 -2
  7. data/docs/README_ranges.rdoc +11 -2
  8. data/examples/example_ruby_library.rb +27 -0
  9. data/extconf.rb +13 -0
  10. data/lib/robust_excel_ole.rb +4 -3
  11. data/lib/robust_excel_ole/{address.rb → address_tool.rb} +23 -22
  12. data/lib/robust_excel_ole/{reo_common.rb → base.rb} +2 -90
  13. data/lib/robust_excel_ole/bookstore.rb +14 -77
  14. data/lib/robust_excel_ole/cell.rb +30 -18
  15. data/lib/robust_excel_ole/excel.rb +71 -41
  16. data/lib/robust_excel_ole/general.rb +39 -14
  17. data/lib/robust_excel_ole/range.rb +42 -19
  18. data/lib/robust_excel_ole/range_owners.rb +40 -25
  19. data/lib/robust_excel_ole/vba_objects.rb +30 -0
  20. data/lib/robust_excel_ole/version.rb +1 -1
  21. data/lib/robust_excel_ole/workbook.rb +241 -235
  22. data/lib/robust_excel_ole/worksheet.rb +42 -21
  23. data/lib/rubygems_plugin.rb +3 -0
  24. data/robust_excel_ole.gemspec +1 -0
  25. data/spec/address_tool_spec.rb +175 -0
  26. data/spec/{reo_common_spec.rb → base_spec.rb} +11 -30
  27. data/spec/bookstore_spec.rb +3 -3
  28. data/spec/cell_spec.rb +67 -25
  29. data/spec/data/more_data/workbook.xls +0 -0
  30. data/spec/excel_spec.rb +41 -275
  31. data/spec/general_spec.rb +17 -23
  32. data/spec/range_spec.rb +57 -3
  33. data/spec/workbook_spec.rb +7 -75
  34. data/spec/workbook_specs/workbook_misc_spec.rb +11 -21
  35. data/spec/workbook_specs/workbook_open_spec.rb +570 -30
  36. data/spec/workbook_specs/workbook_unobtr_spec.rb +33 -33
  37. data/spec/worksheet_spec.rb +36 -4
  38. metadata +10 -6
  39. data/spec/address_spec.rb +0 -174
@@ -1,11 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ require File.join(File.dirname(__FILE__), './range')
4
+
3
5
  module RobustExcelOle
4
- class Cell < REOCommon
5
- attr_reader :cell
6
6
 
7
- def initialize(win32_cell)
8
- @cell = win32_cell.MergeCells ? win32_cell.MergeArea.Item(1,1) : win32_cell
7
+ class Cell < Range
8
+ #attr_reader :ole_cell
9
+
10
+ def initialize(win32_cell, worksheet)
11
+ super
12
+ ole_cell
9
13
  end
10
14
 
11
15
  def v
@@ -16,25 +20,33 @@ module RobustExcelOle
16
20
  self.Value = value
17
21
  end
18
22
 
23
+ def ole_cell
24
+ @ole_range = @ole_range.MergeArea.Item(1,1) if @ole_range.MergeCells
25
+ end
26
+
27
+ private
28
+
19
29
  # @private
20
30
  def method_missing(name, *args)
21
- #if name.to_s[0,1] =~ /[A-Z]/
22
- if ::JRUBY_BUG_ERRORMESSAGE
23
- begin
24
- @cell.send(name, *args)
25
- rescue Java::OrgRacobCom::ComFailException
26
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
31
+ if name.to_s[0,1] =~ /[A-Z]/
32
+ if ::ERRORMESSAGE_JRUBY_BUG
33
+ begin
34
+ #@ole_cell.send(name, *args)
35
+ @ole_range.send(name, *args)
36
+ rescue Java::OrgRacobCom::ComFailException
37
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
38
+ end
39
+ else
40
+ begin
41
+ #@ole_cell.send(name, *args)
42
+ @ole_range.send(name, *args)
43
+ rescue NoMethodError
44
+ raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
45
+ end
27
46
  end
28
47
  else
29
- begin
30
- @cell.send(name, *args)
31
- rescue NoMethodError
32
- raise VBAMethodMissingError, "unknown VBA property or method #{name.inspect}"
33
- end
48
+ super
34
49
  end
35
- # else
36
- # super
37
- # end
38
50
  end
39
51
  end
40
52
  end
@@ -3,8 +3,6 @@
3
3
  require 'weakref'
4
4
  require 'Win32API'
5
5
 
6
-
7
-
8
6
  def ka
9
7
  Excel.kill_all
10
8
  end
@@ -16,9 +14,10 @@ module RobustExcelOle
16
14
  # that you would apply for an Application object.
17
15
  # See https://docs.microsoft.com/en-us/office/vba/api/excel.application(object)#methods
18
16
 
19
- class Excel < RangeOwners
20
- attr_accessor :ole_excel
17
+ class Excel < VbaObjects
18
+ attr_reader :ole_excel
21
19
  attr_reader :properties
20
+ attr_reader :address_tool
22
21
 
23
22
  alias ole_object ole_excel
24
23
 
@@ -111,10 +110,8 @@ module RobustExcelOle
111
110
  # @return [Excel] an Excel instance
112
111
  def recreate(opts = {})
113
112
  unless alive?
114
- opts = {
115
- :visible => @properties[:visible] || false,
116
- :displayalerts => @properties[:displayalerts] || :if_visible
117
- }.merge(opts)
113
+ opts = {:visible => false, :displayalerts => :if_visible}.merge(
114
+ {:visible => @properties[:visible], :displayalerts => @properties[:displayalerts]}).merge(opts)
118
115
  @ole_excel = WIN32OLE.new('Excel.Application')
119
116
  set_options(opts)
120
117
  if opts[:reopen_workbooks]
@@ -127,10 +124,18 @@ module RobustExcelOle
127
124
  self
128
125
  end
129
126
 
127
+ # @private
128
+ def address_tool
129
+ raise(ExcelREOError, "Excel contains no workbook") unless @ole_excel.Workbooks.Count > 0
130
+ @address_tool ||= begin
131
+ address_string = @ole_excel.Workbooks.Item(1).Worksheets.Item(1).Cells.Item(1,1).Address(true,true,XlR1C1)
132
+ address_tool_class.new(address_string)
133
+ end
134
+ end
135
+
130
136
  private
131
137
 
132
138
  # retain the saved status of all workbooks
133
- # @private
134
139
  def retain_saved_workbooks
135
140
  saved_stati = @ole_excel.Workbooks.map { |w| w.Saved }
136
141
  begin
@@ -140,7 +145,6 @@ module RobustExcelOle
140
145
  end
141
146
  end
142
147
 
143
- # @private
144
148
  def ole_workbooks
145
149
  ole_workbooks = begin
146
150
  @ole_excel.Workbooks
@@ -155,11 +159,13 @@ module RobustExcelOle
155
159
 
156
160
  public
157
161
 
162
+ # @private
158
163
  def self.contains_unsaved_workbooks?
159
164
  !Excel.current.unsaved_workbooks.empty?
160
165
  end
161
166
 
162
167
  # returns unsaved workbooks (win32ole objects)
168
+ # @private
163
169
  def unsaved_workbooks
164
170
  unsaved_workbooks = []
165
171
  begin
@@ -177,6 +183,7 @@ module RobustExcelOle
177
183
  # :forget -> closes the Excel instance without saving the workbooks
178
184
  # :save -> saves the workbooks before closing
179
185
  # :alert -> let Excel do it
186
+ # @private
180
187
  def close_workbooks(options = { :if_unsaved => :raise })
181
188
  return unless alive?
182
189
 
@@ -384,14 +391,13 @@ module RobustExcelOle
384
391
  @@hwnd2excel.size
385
392
  end
386
393
 
387
- #private
394
+ private
388
395
 
389
396
  # returns a Win32OLE object that represents a Excel instance to which Excel connects
390
397
  # connects to the first opened Excel instance
391
398
  # if this Excel instance is being closed, then Excel creates a new Excel instance
392
- # @private
393
399
  def self.current_ole_excel
394
- if ::JRUBY_BUG_CONNECT
400
+ if ::CONNECT_EXCEL_JRUBY_BUG
395
401
  result = known_excel_instance
396
402
  if result.nil?
397
403
  if excels_number > 0
@@ -432,6 +438,25 @@ module RobustExcelOle
432
438
  nil
433
439
  end
434
440
 
441
+ def self.hwnd2excel(hwnd)
442
+ excel_weakref = @@hwnd2excel[hwnd]
443
+ if excel_weakref
444
+ if excel_weakref.weakref_alive?
445
+ excel_weakref.__getobj__
446
+ else
447
+ trace 'dead reference to an Excel'
448
+ begin
449
+ @@hwnd2excel.delete(hwnd)
450
+ nil
451
+ rescue
452
+ trace "Warning: deleting dead reference failed! (hwnd: #{hwnd.inspect})"
453
+ end
454
+ end
455
+ end
456
+ end
457
+
458
+ public
459
+
435
460
  # returns all Excel objects for all Excel instances opened with RobustExcelOle
436
461
  def self.known_excel_instances
437
462
  pid2excel = {}
@@ -466,24 +491,6 @@ module RobustExcelOle
466
491
  self
467
492
  end
468
493
 
469
- # @private
470
- def self.hwnd2excel(hwnd)
471
- excel_weakref = @@hwnd2excel[hwnd]
472
- if excel_weakref
473
- if excel_weakref.weakref_alive?
474
- excel_weakref.__getobj__
475
- else
476
- trace 'dead reference to an Excel'
477
- begin
478
- @@hwnd2excel.delete(hwnd)
479
- nil
480
- rescue
481
- trace "Warning: deleting dead reference failed! (hwnd: #{hwnd.inspect})"
482
- end
483
- end
484
- end
485
- end
486
-
487
494
  # @private
488
495
  def hwnd
489
496
  self.Hwnd
@@ -694,18 +701,16 @@ module RobustExcelOle
694
701
  # @param [String] name the name of the range
695
702
  # @param [Variant] value the contents of the range
696
703
  def []=(name, value)
697
- old_color_if_modified = workbook.color_if_modified
698
- workbook.color_if_modified = 42 unless workbook.nil? # aqua-marin
699
- set_namevalue_glob(name,value)
700
- workbook.color_if_modified = old_color_if_modified
704
+ set_namevalue_glob(name, value, :color => 42)
701
705
  end
702
706
 
703
707
  # @private
704
708
  # returns active workbook
705
709
  def workbook
706
- return @workbook unless @workbook.nil?
707
- @workbook = workbook_class.new(@ole_excel.ActiveWorkbook)
708
- end
710
+ @workbook ||= workbook_class.new(@ole_excel.ActiveWorkbook) if @ole_excel.Workbooks.Count > 0
711
+ end
712
+
713
+ alias_method :active_workbook, :workbook
709
714
 
710
715
  # @private
711
716
  def to_s
@@ -732,15 +737,30 @@ module RobustExcelOle
732
737
  self.class.workbook_class
733
738
  end
734
739
 
740
+ # @private
741
+ def self.address_tool_class
742
+ @address_tool_class ||= begin
743
+ module_name = parent_name
744
+ "#{module_name}::AddressTool".constantize
745
+ rescue NameError => e
746
+ AddressTool
747
+ end
748
+ end
749
+
750
+ # @private
751
+ def address_tool_class
752
+ self.class.address_tool_class
753
+ end
754
+
755
+
735
756
  include MethodHelpers
736
757
 
737
758
  private
738
759
 
739
- # @private
740
760
  def method_missing(name, *args)
741
761
  if name.to_s[0,1] =~ /[A-Z]/
742
762
  raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
743
- if ::JRUBY_BUG_ERRORMESSAGE
763
+ if ::ERRORMESSAGE_JRUBY_BUG
744
764
  begin
745
765
  @ole_excel.send(name, *args)
746
766
  rescue Java::OrgRacobCom::ComFailException => msg
@@ -759,7 +779,16 @@ module RobustExcelOle
759
779
  end
760
780
  end
761
781
 
762
- public
782
+ public
783
+
784
+ # @private
785
+ class ExcelDamaged < ExcelREOError
786
+ end
787
+
788
+ # @private
789
+ class UnsavedWorkbooks < ExcelREOError
790
+ end
791
+
763
792
 
764
793
  Application = Excel
765
794
 
@@ -768,3 +797,4 @@ end
768
797
  class WIN32OLE
769
798
  include Enumerable
770
799
  end
800
+
@@ -3,24 +3,47 @@
3
3
  module General
4
4
 
5
5
  IS_JRUBY_PLATFORM = (RUBY_PLATFORM =~ /java/)
6
- ::JRUBY_BUG_CONNECT = IS_JRUBY_PLATFORM && true
7
- ::JRUBY_BUG_COPYSHEETS = IS_JRUBY_PLATFORM && true
8
- ::JRUBY_BUG_ERRORMESSAGE = IS_JRUBY_PLATFORM && true
9
- ::JRUBY_BUG_CONNECTEXCEL = IS_JRUBY_PLATFORM && true
10
- ::JRUBY_BUG_RANGES = IS_JRUBY_PLATFORM && true
11
-
6
+ ::EXPANDPATH_JRUBY_BUG = IS_JRUBY_PLATFORM && true
7
+ ::CONNECT_JRUBY_BUG = IS_JRUBY_PLATFORM && true
8
+ ::COPYSHEETS_JRUBY_BUG = IS_JRUBY_PLATFORM && true
9
+ ::ERRORMESSAGE_JRUBY_BUG = IS_JRUBY_PLATFORM && true
10
+ ::CONNECT_EXCEL_JRUBY_BUG = IS_JRUBY_PLATFORM && true
11
+ ::RANGES_JRUBY_BUG = IS_JRUBY_PLATFORM && true
12
+
13
+ @private
14
+ def network2hostnamesharepath(filename)
15
+ network = WIN32OLE.new('WScript.Network')
16
+ drives = network.enumnetworkdrives
17
+ drive_letter, filename_after_drive_letter = filename.split(':')
18
+ # if filename starts with a drive letter not c and this drive exists,
19
+ # then determine the corresponding host_share_path
20
+ default_drive = File.absolute_path(".")[0]
21
+ if drive_letter != default_drive && drive_letter != filename
22
+ for i in 0 .. drives.Count-1
23
+ next if i % 2 == 1
24
+ if drives.Item(i).gsub(':','') == drive_letter
25
+ hostname_share = drives.Item(i+1) #.gsub('\\','/').gsub('//','')
26
+ break
27
+ end
28
+ end
29
+ hostname_share + filename_after_drive_letter if hostname_share
30
+ else
31
+ return filename
32
+ end
33
+ end
12
34
 
13
35
  # @private
14
- def absolute_path(file)
36
+ def absolute_path(file)
37
+ file[0,2] = './' if ::EXPANDPATH_JRUBY_BUG && file =~ /[A-Z]:[^\/]/
15
38
  file = File.expand_path(file)
16
39
  file = RobustExcelOle::Cygwin.cygpath('-w', file) if RUBY_PLATFORM =~ /cygwin/
17
40
  WIN32OLE.new('Scripting.FileSystemObject').GetAbsolutePathName(file).tr('/','\\')
18
41
  end
19
42
 
20
43
  # @private
21
- def canonize(filename)
44
+ def canonize(filename)
22
45
  raise TypeREOError, "No string given to canonize, but #{filename.inspect}" unless filename.is_a?(String)
23
-
46
+ filename = network2hostnamesharepath(filename)
24
47
  normalize(filename).downcase
25
48
  end
26
49
 
@@ -72,26 +95,28 @@ end
72
95
 
73
96
  # @private
74
97
  class WIN32OLE
75
-
76
- include RobustExcelOle
77
98
 
78
99
  # type-lifting WIN32OLE objects to RobustExcelOle objects
79
100
  def to_reo
80
- class2method = [{Excel => :Hwnd}, {Workbook => :FullName}, {Worksheet => :Copy}, {Range => :Address}]
101
+ class2method = [{Excel => :Hwnd}, {Workbook => :FullName}, {Worksheet => :Copy}, {RobustExcelOle::Range => :Row}]
81
102
  class2method.each do |element|
82
103
  classname = element.first.first
83
104
  method = element.first.last
84
105
  begin
85
106
  self.send(method)
86
- return classname.new(self)
107
+ if classname == RobustExcelOle::Range && self.Rows.Count == 1 && self.Columns.Count == 1
108
+ return Cell.new(self)
109
+ else
110
+ return classname.new(self)
111
+ end
87
112
  rescue
88
113
  next
89
114
  end
90
115
  end
116
+ raise TypeREOError, "given object cannot be type-lifted to a RobustExcelOle object"
91
117
  end
92
118
  end
93
119
 
94
-
95
120
  # @private
96
121
  class ::String
97
122
  def / path_part
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+
2
3
  module RobustExcelOle
3
4
 
4
5
  # This class essentially wraps a Win32Ole Range object.
@@ -6,28 +7,44 @@ module RobustExcelOle
6
7
  # that you would apply for a Range object.
7
8
  # See https://docs.microsoft.com/en-us/office/vba/api/excel.worksheet#methods
8
9
 
9
- class Range < REOCommon
10
+ class Range < VbaObjects
10
11
  include Enumerable
11
12
  attr_reader :ole_range
12
13
  attr_reader :worksheet
13
14
 
14
- def initialize(win32_range)
15
+ def initialize(win32_range, worksheet = nil)
15
16
  @ole_range = win32_range
16
- @worksheet = worksheet_class.new(self.Parent)
17
+ @worksheet = worksheet ? worksheet : worksheet_class.new(self.Parent)
18
+ #@worksheet = worksheet_class.new(self.Parent)
17
19
  end
18
20
 
19
21
  def each
20
- @ole_range.each do |row_or_column|
21
- yield RobustExcelOle::Cell.new(row_or_column)
22
+ @ole_range.each_with_index do |ole_cell, index|
23
+ yield cell(index){ole_cell}
22
24
  end
23
25
  end
24
26
 
27
+ def [] index
28
+ cell(index) {
29
+ @ole_range.Cells.Item(index + 1)
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def cell(index)
36
+ @cells ||= []
37
+ @cells[index + 1] ||= RobustExcelOle::Cell.new(yield,@worksheet)
38
+ end
39
+
40
+ public
41
+
25
42
  # returns flat array of the values of a given range
26
43
  # @params [Range] a range
27
44
  # @returns [Array] the values
28
45
  def values(range = nil)
29
46
  #result = map { |x| x.Value }.flatten
30
- result_unflatten = if !::JRUBY_BUG_RANGES
47
+ result_unflatten = if !::RANGES_JRUBY_BUG
31
48
  map { |x| x.v }
32
49
  else
33
50
  self.v
@@ -44,11 +61,11 @@ module RobustExcelOle
44
61
 
45
62
  def v
46
63
  begin
47
- if !::JRUBY_BUG_RANGES
64
+ if !::RANGES_JRUBY_BUG
48
65
  self.Value
49
66
  else
50
67
  address_r1c1 = self.AddressLocal(true,true,XlR1C1)
51
- row, col = Address.int_range(address_r1c1)
68
+ row, col = address_tool.as_integer_ranges(address_r1c1)
52
69
  values = []
53
70
  row.each do |r|
54
71
  values_col = []
@@ -65,11 +82,11 @@ module RobustExcelOle
65
82
 
66
83
  def v=(value)
67
84
  begin
68
- if !::JRUBY_BUG_RANGES
85
+ if !::RANGES_JRUBY_BUG
69
86
  ole_range.Value = value
70
87
  else
71
88
  address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
72
- row, col = Address.int_range(address_r1c1)
89
+ row, col = address_tool.as_integer_ranges(address_r1c1)
73
90
  row.each_with_index do |r,i|
74
91
  col.each_with_index do |c,j|
75
92
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
@@ -82,10 +99,8 @@ module RobustExcelOle
82
99
  end
83
100
  end
84
101
 
85
- def [] index
86
- @cells = []
87
- @cells[index + 1] = RobustExcelOle::Cell.new(@ole_range.Cells.Item(index + 1))
88
- end
102
+ alias_method :value, :v
103
+ alias_method :value=, :v=
89
104
 
90
105
  # copies a range
91
106
  # @params [Address or Address-Array] address or upper left position of the destination range
@@ -115,8 +130,7 @@ module RobustExcelOle
115
130
  { }
116
131
  end
117
132
  end
118
- rows, columns = Address.int_range(dest_address)
119
- #dest_sheet = @worksheet if dest_sheet == :__not_provided
133
+ rows, columns = address_tool.as_integer_ranges(dest_address)
120
134
  dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
121
135
  dest_range_address = if (not dest_address_is_position)
122
136
  [rows.min..rows.max,columns.min..columns.max]
@@ -168,7 +182,7 @@ module RobustExcelOle
168
182
  # @options [Worksheet] the destination worksheet
169
183
  # @options [Hash] options: :transpose, :values_only
170
184
  def copy_special(dest_address, dest_sheet = :__not_provided, options = { })
171
- rows, columns = Address.int_range(dest_address)
185
+ rows, columns = address_tool.as_integer_ranges(dest_address)
172
186
  dest_sheet = @worksheet if dest_sheet == :__not_provided
173
187
  dest_address_is_position = (rows.min == rows.max && columns.min == columns.max)
174
188
  dest_range_address = if (not dest_address_is_position)
@@ -220,6 +234,11 @@ module RobustExcelOle
220
234
  self.Address == other_range.Address
221
235
  end
222
236
 
237
+ # @private
238
+ def excel
239
+ @worksheet.workbook.excel
240
+ end
241
+
223
242
  # @private
224
243
  def self.worksheet_class
225
244
  @worksheet_class ||= begin
@@ -237,10 +256,9 @@ module RobustExcelOle
237
256
 
238
257
  private
239
258
 
240
- # @private
241
259
  def method_missing(name, *args)
242
260
  if name.to_s[0,1] =~ /[A-Z]/
243
- if ::JRUBY_BUG_ERRORMESSAGE
261
+ if ::ERRORMESSAGE_JRUBY_BUG
244
262
  begin
245
263
  @ole_range.send(name, *args)
246
264
  rescue Java::OrgRacobCom::ComFailException
@@ -258,4 +276,9 @@ module RobustExcelOle
258
276
  end
259
277
  end
260
278
  end
279
+
280
+ # @private
281
+ class RangeNotCopied < MiscREOError
282
+ end
283
+
261
284
  end