robust_excel_ole 1.16 → 1.18.3

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +23 -0
  3. data/README.rdoc +17 -0
  4. data/benchmarking/Gemfile +7 -0
  5. data/benchmarking/README.md +131 -0
  6. data/benchmarking/creek_example.rb +33 -0
  7. data/benchmarking/generating_excel_files.rb +28 -0
  8. data/benchmarking/reo_example.rb +26 -0
  9. data/benchmarking/reo_example1.rb +31 -0
  10. data/benchmarking/reo_example2.rb +26 -0
  11. data/benchmarking/roo_example.rb +33 -0
  12. data/benchmarking/ruby_xl_example.rb +36 -0
  13. data/benchmarking/sample_excel_files/xlsx_500_rows.xlsx +0 -0
  14. data/benchmarking/simple_xlsx_reader_example.rb +33 -0
  15. data/benchmarking/spreadsheet_example.rb +36 -0
  16. data/bin/jreo.bat +3 -0
  17. data/bin/reo.bat +3 -0
  18. data/docs/README_excel.rdoc +6 -0
  19. data/docs/README_open.rdoc +4 -0
  20. data/docs/README_ranges.rdoc +15 -0
  21. data/examples/example_ruby_library.rb +27 -0
  22. data/lib/robust_excel_ole.rb +4 -3
  23. data/lib/robust_excel_ole/{address.rb → address_tool.rb} +23 -22
  24. data/lib/robust_excel_ole/{reo_common.rb → base.rb} +2 -90
  25. data/lib/robust_excel_ole/bookstore.rb +2 -2
  26. data/lib/robust_excel_ole/cell.rb +30 -18
  27. data/lib/robust_excel_ole/excel.rb +65 -30
  28. data/lib/robust_excel_ole/general.rb +7 -5
  29. data/lib/robust_excel_ole/range.rb +38 -15
  30. data/lib/robust_excel_ole/range_owners.rb +26 -11
  31. data/lib/robust_excel_ole/vba_objects.rb +30 -0
  32. data/lib/robust_excel_ole/version.rb +1 -1
  33. data/lib/robust_excel_ole/workbook.rb +136 -147
  34. data/lib/robust_excel_ole/worksheet.rb +47 -15
  35. data/lib/rubygems_plugin.rb +3 -0
  36. data/robust_excel_ole.gemspec +1 -1
  37. data/spec/address_tool_spec.rb +175 -0
  38. data/spec/{reo_common_spec.rb → base_spec.rb} +10 -29
  39. data/spec/cell_spec.rb +67 -25
  40. data/spec/data/more_data/workbook.xls +0 -0
  41. data/spec/excel_spec.rb +41 -273
  42. data/spec/general_spec.rb +15 -19
  43. data/spec/range_spec.rb +57 -3
  44. data/spec/workbook_spec.rb +7 -75
  45. data/spec/workbook_specs/workbook_misc_spec.rb +10 -10
  46. data/spec/workbook_specs/workbook_open_spec.rb +228 -14
  47. data/spec/workbook_specs/workbook_unobtr_spec.rb +31 -31
  48. data/spec/worksheet_spec.rb +42 -0
  49. metadata +27 -8
  50. data/spec/address_spec.rb +0 -174
@@ -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
17
+ class Excel < VbaObjects
20
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
 
@@ -125,10 +124,18 @@ module RobustExcelOle
125
124
  self
126
125
  end
127
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
+
128
136
  private
129
137
 
130
138
  # retain the saved status of all workbooks
131
- # @private
132
139
  def retain_saved_workbooks
133
140
  saved_stati = @ole_excel.Workbooks.map { |w| w.Saved }
134
141
  begin
@@ -138,7 +145,6 @@ module RobustExcelOle
138
145
  end
139
146
  end
140
147
 
141
- # @private
142
148
  def ole_workbooks
143
149
  ole_workbooks = begin
144
150
  @ole_excel.Workbooks
@@ -153,11 +159,13 @@ module RobustExcelOle
153
159
 
154
160
  public
155
161
 
162
+ # @private
156
163
  def self.contains_unsaved_workbooks?
157
164
  !Excel.current.unsaved_workbooks.empty?
158
165
  end
159
166
 
160
167
  # returns unsaved workbooks (win32ole objects)
168
+ # @private
161
169
  def unsaved_workbooks
162
170
  unsaved_workbooks = []
163
171
  begin
@@ -175,6 +183,7 @@ module RobustExcelOle
175
183
  # :forget -> closes the Excel instance without saving the workbooks
176
184
  # :save -> saves the workbooks before closing
177
185
  # :alert -> let Excel do it
186
+ # @private
178
187
  def close_workbooks(options = { :if_unsaved => :raise })
179
188
  return unless alive?
180
189
 
@@ -382,12 +391,11 @@ module RobustExcelOle
382
391
  @@hwnd2excel.size
383
392
  end
384
393
 
385
- #private
394
+ private
386
395
 
387
396
  # returns a Win32OLE object that represents a Excel instance to which Excel connects
388
397
  # connects to the first opened Excel instance
389
398
  # if this Excel instance is being closed, then Excel creates a new Excel instance
390
- # @private
391
399
  def self.current_ole_excel
392
400
  if ::CONNECT_EXCEL_JRUBY_BUG
393
401
  result = known_excel_instance
@@ -430,6 +438,25 @@ module RobustExcelOle
430
438
  nil
431
439
  end
432
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
+
433
460
  # returns all Excel objects for all Excel instances opened with RobustExcelOle
434
461
  def self.known_excel_instances
435
462
  pid2excel = {}
@@ -464,24 +491,6 @@ module RobustExcelOle
464
491
  self
465
492
  end
466
493
 
467
- # @private
468
- def self.hwnd2excel(hwnd)
469
- excel_weakref = @@hwnd2excel[hwnd]
470
- if excel_weakref
471
- if excel_weakref.weakref_alive?
472
- excel_weakref.__getobj__
473
- else
474
- trace 'dead reference to an Excel'
475
- begin
476
- @@hwnd2excel.delete(hwnd)
477
- nil
478
- rescue
479
- trace "Warning: deleting dead reference failed! (hwnd: #{hwnd.inspect})"
480
- end
481
- end
482
- end
483
- end
484
-
485
494
  # @private
486
495
  def hwnd
487
496
  self.Hwnd
@@ -698,9 +707,10 @@ module RobustExcelOle
698
707
  # @private
699
708
  # returns active workbook
700
709
  def workbook
701
- return @workbook unless @workbook.nil?
702
- @workbook = workbook_class.new(@ole_excel.ActiveWorkbook)
703
- end
710
+ @workbook ||= workbook_class.new(@ole_excel.ActiveWorkbook) if @ole_excel.Workbooks.Count > 0
711
+ end
712
+
713
+ alias_method :active_workbook, :workbook
704
714
 
705
715
  # @private
706
716
  def to_s
@@ -727,11 +737,26 @@ module RobustExcelOle
727
737
  self.class.workbook_class
728
738
  end
729
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
+
730
756
  include MethodHelpers
731
757
 
732
758
  private
733
759
 
734
- # @private
735
760
  def method_missing(name, *args)
736
761
  if name.to_s[0,1] =~ /[A-Z]/
737
762
  raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
@@ -754,7 +779,16 @@ module RobustExcelOle
754
779
  end
755
780
  end
756
781
 
757
- public
782
+ public
783
+
784
+ # @private
785
+ class ExcelDamaged < ExcelREOError
786
+ end
787
+
788
+ # @private
789
+ class UnsavedWorkbooks < ExcelREOError
790
+ end
791
+
758
792
 
759
793
  Application = Excel
760
794
 
@@ -763,3 +797,4 @@ end
763
797
  class WIN32OLE
764
798
  include Enumerable
765
799
  end
800
+
@@ -95,26 +95,28 @@ end
95
95
 
96
96
  # @private
97
97
  class WIN32OLE
98
-
99
- include RobustExcelOle
100
98
 
101
99
  # type-lifting WIN32OLE objects to RobustExcelOle objects
102
100
  def to_reo
103
- class2method = [{Excel => :Hwnd}, {Workbook => :FullName}, {Worksheet => :Copy}, {Range => :Address}]
101
+ class2method = [{Excel => :Hwnd}, {Workbook => :FullName}, {Worksheet => :Copy}, {RobustExcelOle::Range => :Row}]
104
102
  class2method.each do |element|
105
103
  classname = element.first.first
106
104
  method = element.first.last
107
105
  begin
108
106
  self.send(method)
109
- 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
110
112
  rescue
111
113
  next
112
114
  end
113
115
  end
116
+ raise TypeREOError, "given object cannot be type-lifted to a RobustExcelOle object"
114
117
  end
115
118
  end
116
119
 
117
-
118
120
  # @private
119
121
  class ::String
120
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,22 +7,38 @@ 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
@@ -48,7 +65,7 @@ module RobustExcelOle
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 = []
@@ -69,7 +86,7 @@ module RobustExcelOle
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,7 +256,6 @@ 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
261
  if ::ERRORMESSAGE_JRUBY_BUG
@@ -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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RobustExcelOle
4
4
 
5
- class RangeOwners < REOCommon
5
+ class RangeOwners < VbaObjects
6
6
 
7
7
  # returns the contents of a range with given name
8
8
  # if the name could not be found or the value could not be determined,
@@ -22,12 +22,13 @@ module RobustExcelOle
22
22
  raise
23
23
  end
24
24
  ole_range = name_obj.RefersToRange
25
+ worksheet = self if self.is_a?(Worksheet)
25
26
  value = begin
26
27
  #name_obj.RefersToRange.Value
27
28
  if !::RANGES_JRUBY_BUG
28
29
  ole_range.Value
29
30
  else
30
- values = RobustExcelOle::Range.new(ole_range).v
31
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
31
32
  (values.size==1 && values.first.size==1) ? values.first.first : values
32
33
  end
33
34
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -42,7 +43,7 @@ module RobustExcelOle
42
43
  if !::RANGES_JRUBY_BUG
43
44
  ole_range.Value
44
45
  else
45
- values = RobustExcelOle::Range.new(ole_range).v
46
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
46
47
  (values.size==1 && values.first.size==1) ? values.first.first : values
47
48
  end
48
49
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -75,7 +76,7 @@ module RobustExcelOle
75
76
  ole_range.Value = value
76
77
  else
77
78
  address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
78
- row, col = Address.int_range(address_r1c1)
79
+ row, col = address_tool.as_integer_ranges(address_r1c1)
79
80
  row.each_with_index do |r,i|
80
81
  col.each_with_index do |c,j|
81
82
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value )
@@ -105,11 +106,12 @@ module RobustExcelOle
105
106
  raise NameNotFound, "name #{name.inspect} not in #{self.inspect}"
106
107
  end
107
108
  begin
109
+ worksheet = self if self.is_a?(Worksheet)
108
110
  #value = ole_range.Value
109
111
  value = if !::RANGES_JRUBY_BUG
110
112
  ole_range.Value
111
113
  else
112
- values = RobustExcelOle::Range.new(ole_range).v
114
+ values = RobustExcelOle::Range.new(ole_range, worksheet).v
113
115
  (values.size==1 && values.first.size==1) ? values.first.first : values
114
116
  end
115
117
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
@@ -141,7 +143,7 @@ module RobustExcelOle
141
143
  ole_range.Value = value
142
144
  else
143
145
  address_r1c1 = ole_range.AddressLocal(true,true,XlR1C1)
144
- row, col = Address.int_range(address_r1c1)
146
+ row, col = address_tool.as_integer_ranges(address_r1c1)
145
147
  row.each_with_index do |r,i|
146
148
  col.each_with_index do |c,j|
147
149
  ole_range.Cells(i+1,j+1).Value = (value.respond_to?(:first) ? value[i][j] : value)
@@ -180,10 +182,11 @@ module RobustExcelOle
180
182
  # @return [Range] a range
181
183
  def range(name_or_address, address2 = :__not_provided)
182
184
  begin
185
+ worksheet = self if self.is_a?(Worksheet)
183
186
  if address2 == :__not_provided
184
187
  range = if name_or_address.is_a?(String)
185
188
  begin
186
- RobustExcelOle::Range.new(name_object(name_or_address).RefersToRange)
189
+ RobustExcelOle::Range.new(name_object(name_or_address).RefersToRange, worksheet)
187
190
  rescue NameNotFound
188
191
  nil
189
192
  end
@@ -192,8 +195,8 @@ module RobustExcelOle
192
195
  if self.is_a?(Worksheet) && (range.nil? || (address2 != :__not_provided))
193
196
  address = name_or_address
194
197
  address = [name_or_address,address2] unless address2 == :__not_provided
195
- self.Names.Add('__dummy001',nil,true,nil,nil,nil,nil,nil,nil,'=' + Address.r1c1(address))
196
- range = RobustExcelOle::Range.new(name_object('__dummy001').RefersToRange)
198
+ self.Names.Add('__dummy001',nil,true,nil,nil,nil,nil,nil,nil,'=' + address_tool.as_r1c1(address))
199
+ range = RobustExcelOle::Range.new(name_object('__dummy001').RefersToRange, worksheet)
197
200
  self.Names.Item('__dummy001').Delete
198
201
  workbook = self.is_a?(Workbook) ? self : self.workbook
199
202
  workbook.save
@@ -216,7 +219,7 @@ module RobustExcelOle
216
219
  def add_name(name, addr, addr_deprecated = :__not_provided)
217
220
  addr = [addr,addr_deprecated] unless addr_deprecated == :__not_provided
218
221
  begin
219
- self.Names.Add(name, nil, true, nil, nil, nil, nil, nil, nil, '=' + Address.r1c1(addr))
222
+ self.Names.Add(name, nil, true, nil, nil, nil, nil, nil, nil, '=' + address_tool.as_r1c1(addr))
220
223
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
221
224
  raise RangeNotEvaluatable, "cannot add name #{name.inspect} to range #{addr.inspect}"
222
225
  end
@@ -257,7 +260,7 @@ module RobustExcelOle
257
260
  rescue WIN32OLERuntimeError, Java::OrgRacobCom::ComFailException
258
261
  raise UnexpectedREOError, "name error in #{File.basename(self.stored_filename).inspect}"
259
262
  end
260
- end
263
+ end
261
264
 
262
265
  private
263
266
 
@@ -273,4 +276,16 @@ module RobustExcelOle
273
276
 
274
277
  end
275
278
 
279
+ # @private
280
+ class NameNotFound < NamesREOError
281
+ end
282
+
283
+ # @private
284
+ class NameAlreadyExists < NamesREOError
285
+ end
286
+
287
+ # @private
288
+ class RangeNotCreated < MiscREOError
289
+ end
290
+
276
291
  end