robust_excel_ole 1.13 → 1.18
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.
- checksums.yaml +4 -4
- data/Changelog +47 -4
- data/README.rdoc +52 -47
- data/___dummy_workbook.xls +0 -0
- data/docs/README_excel.rdoc +9 -3
- data/docs/README_open.rdoc +86 -44
- data/docs/README_ranges.rdoc +90 -81
- data/docs/README_save_close.rdoc +9 -9
- data/docs/README_sheet.rdoc +17 -17
- data/examples/example_ruby_library.rb +27 -0
- data/extconf.rb +7 -0
- data/lib/robust_excel_ole.rb +7 -4
- data/lib/robust_excel_ole/{address.rb → address_tool.rb} +23 -22
- data/lib/robust_excel_ole/{reo_common.rb → base.rb} +3 -92
- data/lib/robust_excel_ole/bookstore.rb +14 -77
- data/lib/robust_excel_ole/cell.rb +30 -18
- data/lib/robust_excel_ole/excel.rb +138 -92
- data/lib/robust_excel_ole/general.rb +40 -15
- data/lib/robust_excel_ole/range.rb +42 -19
- data/lib/robust_excel_ole/range_owners.rb +40 -25
- data/lib/robust_excel_ole/vba_objects.rb +30 -0
- data/lib/robust_excel_ole/version.rb +1 -1
- data/lib/robust_excel_ole/workbook.rb +320 -319
- data/lib/robust_excel_ole/worksheet.rb +51 -25
- data/robust_excel_ole.gemspec +1 -0
- data/spec/address_tool_spec.rb +175 -0
- data/spec/{reo_common_spec.rb → base_spec.rb} +19 -34
- data/spec/bookstore_spec.rb +3 -3
- data/spec/cell_spec.rb +67 -25
- data/spec/data/more_data/workbook.xls +0 -0
- data/spec/excel_spec.rb +137 -369
- data/spec/general_spec.rb +21 -27
- data/spec/range_spec.rb +57 -3
- data/spec/workbook_spec.rb +11 -79
- data/spec/workbook_specs/workbook_misc_spec.rb +29 -40
- data/spec/workbook_specs/workbook_open_spec.rb +599 -31
- data/spec/workbook_specs/workbook_unobtr_spec.rb +760 -93
- data/spec/worksheet_spec.rb +36 -4
- metadata +12 -7
- 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
|
-
|
8
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
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
|
@@ -14,21 +14,17 @@ module RobustExcelOle
|
|
14
14
|
# that you would apply for an Application object.
|
15
15
|
# See https://docs.microsoft.com/en-us/office/vba/api/excel.application(object)#methods
|
16
16
|
|
17
|
-
class Excel <
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# setter methods are implemented below
|
23
|
-
attr_reader :visible
|
24
|
-
attr_reader :displayalerts
|
25
|
-
attr_reader :calculation
|
26
|
-
attr_reader :screenupdating
|
17
|
+
class Excel < VbaObjects
|
18
|
+
attr_reader :ole_excel
|
19
|
+
attr_reader :properties
|
20
|
+
attr_reader :address_tool
|
27
21
|
|
28
22
|
alias ole_object ole_excel
|
29
23
|
|
30
24
|
@@hwnd2excel = {}
|
31
25
|
|
26
|
+
PROPERTIES = [:visible, :displayalerts, :calculation, :screenupdating]
|
27
|
+
|
32
28
|
# creates a new Excel instance
|
33
29
|
# @param [Hash] options the options
|
34
30
|
# @option options [Variant] :displayalerts
|
@@ -76,12 +72,7 @@ module RobustExcelOle
|
|
76
72
|
ole_xl = win32ole_excel unless win32ole_excel.nil?
|
77
73
|
options = { :reuse => true }.merge(options)
|
78
74
|
if options[:reuse] == true && ole_xl.nil?
|
79
|
-
ole_xl =
|
80
|
-
excel_instance = known_excel_instance
|
81
|
-
excel_instance.ole_excel unless excel_instance.nil?
|
82
|
-
else
|
83
|
-
current_excel
|
84
|
-
end
|
75
|
+
ole_xl = current_ole_excel
|
85
76
|
end
|
86
77
|
connected = (not ole_xl.nil?) && win32ole_excel.nil?
|
87
78
|
ole_xl ||= WIN32OLE.new('Excel.Application')
|
@@ -101,10 +92,7 @@ module RobustExcelOle
|
|
101
92
|
unless reused || connected
|
102
93
|
options = { :displayalerts => :if_visible, :visible => false, :screenupdating => true }.merge(options)
|
103
94
|
end
|
104
|
-
result.
|
105
|
-
result.displayalerts = options[:displayalerts] unless options[:displayalerts].nil?
|
106
|
-
result.calculation = options[:calculation] unless options[:calculation].nil?
|
107
|
-
result.screenupdating = options[:screenupdating] unless options[:screenupdating].nil?
|
95
|
+
result.set_options(options)
|
108
96
|
end
|
109
97
|
result
|
110
98
|
end
|
@@ -122,14 +110,10 @@ module RobustExcelOle
|
|
122
110
|
# @return [Excel] an Excel instance
|
123
111
|
def recreate(opts = {})
|
124
112
|
unless alive?
|
125
|
-
opts = {
|
126
|
-
|
127
|
-
:displayalerts => @displayalerts || :if_visible
|
128
|
-
}.merge(opts)
|
113
|
+
opts = {:visible => false, :displayalerts => :if_visible}.merge(
|
114
|
+
{:visible => @properties[:visible], :displayalerts => @properties[:displayalerts]}).merge(opts)
|
129
115
|
@ole_excel = WIN32OLE.new('Excel.Application')
|
130
|
-
|
131
|
-
self.displayalerts = opts[:displayalerts]
|
132
|
-
self.calculation = opts[:calculation]
|
116
|
+
set_options(opts)
|
133
117
|
if opts[:reopen_workbooks]
|
134
118
|
books = workbook_class.books
|
135
119
|
books.each do |book|
|
@@ -140,37 +124,18 @@ module RobustExcelOle
|
|
140
124
|
self
|
141
125
|
end
|
142
126
|
|
143
|
-
private
|
144
|
-
|
145
|
-
# returns a Win32OLE object that represents a Excel instance to which Excel connects
|
146
|
-
# connects to the first opened Excel instance
|
147
|
-
# if this Excel instance is being closed, then Excel creates a new Excel instance
|
148
127
|
# @private
|
149
|
-
def
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
nil
|
155
|
-
end
|
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)
|
156
133
|
end
|
157
|
-
if result
|
158
|
-
begin
|
159
|
-
result.Visible # send any method, just to see if it responds
|
160
|
-
rescue
|
161
|
-
trace 'dead excel ' + (begin
|
162
|
-
"Window-handle = #{result.HWnd}"
|
163
|
-
rescue
|
164
|
-
'without window handle'
|
165
|
-
end)
|
166
|
-
return nil
|
167
|
-
end
|
168
|
-
end
|
169
|
-
result
|
170
134
|
end
|
171
135
|
|
136
|
+
private
|
137
|
+
|
172
138
|
# retain the saved status of all workbooks
|
173
|
-
# @private
|
174
139
|
def retain_saved_workbooks
|
175
140
|
saved_stati = @ole_excel.Workbooks.map { |w| w.Saved }
|
176
141
|
begin
|
@@ -180,7 +145,6 @@ module RobustExcelOle
|
|
180
145
|
end
|
181
146
|
end
|
182
147
|
|
183
|
-
# @private
|
184
148
|
def ole_workbooks
|
185
149
|
ole_workbooks = begin
|
186
150
|
@ole_excel.Workbooks
|
@@ -195,11 +159,13 @@ module RobustExcelOle
|
|
195
159
|
|
196
160
|
public
|
197
161
|
|
162
|
+
# @private
|
198
163
|
def self.contains_unsaved_workbooks?
|
199
164
|
!Excel.current.unsaved_workbooks.empty?
|
200
165
|
end
|
201
166
|
|
202
167
|
# returns unsaved workbooks (win32ole objects)
|
168
|
+
# @private
|
203
169
|
def unsaved_workbooks
|
204
170
|
unsaved_workbooks = []
|
205
171
|
begin
|
@@ -217,6 +183,7 @@ module RobustExcelOle
|
|
217
183
|
# :forget -> closes the Excel instance without saving the workbooks
|
218
184
|
# :save -> saves the workbooks before closing
|
219
185
|
# :alert -> let Excel do it
|
186
|
+
# @private
|
220
187
|
def close_workbooks(options = { :if_unsaved => :raise })
|
221
188
|
return unless alive?
|
222
189
|
|
@@ -424,6 +391,42 @@ module RobustExcelOle
|
|
424
391
|
@@hwnd2excel.size
|
425
392
|
end
|
426
393
|
|
394
|
+
private
|
395
|
+
|
396
|
+
# returns a Win32OLE object that represents a Excel instance to which Excel connects
|
397
|
+
# connects to the first opened Excel instance
|
398
|
+
# if this Excel instance is being closed, then Excel creates a new Excel instance
|
399
|
+
def self.current_ole_excel
|
400
|
+
if ::CONNECT_EXCEL_JRUBY_BUG
|
401
|
+
result = known_excel_instance
|
402
|
+
if result.nil?
|
403
|
+
if excels_number > 0
|
404
|
+
dummy_ole_workbook = WIN32OLE.connect(General.absolute_path('___dummy_workbook.xls')) rescue nil
|
405
|
+
result = dummy_ole_workbook.Application
|
406
|
+
visible_status = result.Visible
|
407
|
+
dummy_ole_workbook.Close
|
408
|
+
dummy_ole_workbook = nil
|
409
|
+
result.Visible = visible_status
|
410
|
+
end
|
411
|
+
end
|
412
|
+
else
|
413
|
+
result = WIN32OLE.connect('Excel.Application') rescue nil
|
414
|
+
end
|
415
|
+
if result
|
416
|
+
begin
|
417
|
+
result.Visible # send any method, just to see if it responds
|
418
|
+
rescue
|
419
|
+
trace 'dead excel ' + (begin
|
420
|
+
"Window-handle = #{result.HWnd}"
|
421
|
+
rescue
|
422
|
+
'without window handle'
|
423
|
+
end)
|
424
|
+
return nil
|
425
|
+
end
|
426
|
+
end
|
427
|
+
result
|
428
|
+
end
|
429
|
+
|
427
430
|
# returns an Excel instance
|
428
431
|
def self.known_excel_instance
|
429
432
|
@@hwnd2excel.each do |hwnd, wr_excel|
|
@@ -435,6 +438,25 @@ module RobustExcelOle
|
|
435
438
|
nil
|
436
439
|
end
|
437
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
|
+
|
438
460
|
# returns all Excel objects for all Excel instances opened with RobustExcelOle
|
439
461
|
def self.known_excel_instances
|
440
462
|
pid2excel = {}
|
@@ -469,24 +491,6 @@ module RobustExcelOle
|
|
469
491
|
self
|
470
492
|
end
|
471
493
|
|
472
|
-
# @private
|
473
|
-
def self.hwnd2excel(hwnd)
|
474
|
-
excel_weakref = @@hwnd2excel[hwnd]
|
475
|
-
if excel_weakref
|
476
|
-
if excel_weakref.weakref_alive?
|
477
|
-
excel_weakref.__getobj__
|
478
|
-
else
|
479
|
-
trace 'dead reference to an Excel'
|
480
|
-
begin
|
481
|
-
@@hwnd2excel.delete(hwnd)
|
482
|
-
nil
|
483
|
-
rescue
|
484
|
-
trace "Warning: deleting dead reference failed! (hwnd: #{hwnd.inspect})"
|
485
|
-
end
|
486
|
-
end
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
494
|
# @private
|
491
495
|
def hwnd
|
492
496
|
self.Hwnd
|
@@ -540,7 +544,7 @@ module RobustExcelOle
|
|
540
544
|
|
541
545
|
# sets DisplayAlerts in a block
|
542
546
|
def with_displayalerts displayalerts_value
|
543
|
-
old_displayalerts = displayalerts
|
547
|
+
old_displayalerts = @properties[:displayalerts]
|
544
548
|
self.displayalerts = displayalerts_value
|
545
549
|
begin
|
546
550
|
yield self
|
@@ -551,26 +555,29 @@ module RobustExcelOle
|
|
551
555
|
|
552
556
|
# makes the current Excel instance visible or invisible
|
553
557
|
def visible= visible_value
|
554
|
-
|
555
|
-
@ole_excel.
|
558
|
+
return if visible_value.nil?
|
559
|
+
@ole_excel.Visible = @properties[:visible] = visible_value
|
560
|
+
@ole_excel.DisplayAlerts = @properties[:visible] if @properties[:displayalerts] == :if_visible
|
556
561
|
end
|
557
562
|
|
558
563
|
# enables DisplayAlerts in the current Excel instance
|
559
564
|
def displayalerts= displayalerts_value
|
560
|
-
|
561
|
-
@
|
565
|
+
return if displayalerts_value.nil?
|
566
|
+
@properties[:displayalerts] = displayalerts_value
|
567
|
+
@ole_excel.DisplayAlerts = @properties[:displayalerts] == :if_visible ? @ole_excel.Visible : displayalerts_value
|
562
568
|
end
|
563
569
|
|
564
570
|
# sets ScreenUpdating
|
565
571
|
def screenupdating= screenupdating_value
|
566
|
-
|
572
|
+
return if screenupdating_value.nil?
|
573
|
+
@ole_excel.ScreenUpdating = @properties[:screenupdating] = screenupdating_value
|
567
574
|
end
|
568
575
|
|
569
576
|
# sets calculation mode
|
570
577
|
# retains the saved-status of the workbooks when set to manual
|
571
578
|
def calculation= calculation_mode
|
572
579
|
return if calculation_mode.nil?
|
573
|
-
@calculation = calculation_mode
|
580
|
+
@properties[:calculation] = calculation_mode
|
574
581
|
calc_mode_changable = @ole_excel.Workbooks.Count > 0 && @ole_excel.Calculation.is_a?(Integer)
|
575
582
|
if calc_mode_changable
|
576
583
|
retain_saved_workbooks do
|
@@ -610,9 +617,9 @@ module RobustExcelOle
|
|
610
617
|
def Calculation= calculation_vba_mode
|
611
618
|
case calculation_vba_mode
|
612
619
|
when XlCalculationManual
|
613
|
-
@calculation = :manual
|
620
|
+
@properties[:calculation] = :manual
|
614
621
|
when XlCalculationAutomatic
|
615
|
-
@calculation = :automatic
|
622
|
+
@properties[:calculation] = :automatic
|
616
623
|
end
|
617
624
|
@ole_excel.Calculation = calculation_vba_mode
|
618
625
|
end
|
@@ -632,13 +639,22 @@ module RobustExcelOle
|
|
632
639
|
|
633
640
|
# set options in this Excel instance
|
634
641
|
def for_this_instance(options)
|
635
|
-
|
642
|
+
set_options(options)
|
643
|
+
#self.class.new(@ole_excel, options)
|
636
644
|
end
|
637
645
|
|
638
|
-
def set_options(options)
|
639
|
-
|
640
|
-
end
|
646
|
+
#def set_options(options)
|
647
|
+
# for_this_instance(options)
|
648
|
+
#end
|
641
649
|
|
650
|
+
def set_options(options)
|
651
|
+
@properties ||= { }
|
652
|
+
PROPERTIES.each do |property|
|
653
|
+
method = (property.to_s + '=').to_sym
|
654
|
+
self.send(method, options[property])
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
642
658
|
# set options in all workbooks
|
643
659
|
def for_all_workbooks(options)
|
644
660
|
each_workbook(options)
|
@@ -685,12 +701,17 @@ module RobustExcelOle
|
|
685
701
|
# @param [String] name the name of the range
|
686
702
|
# @param [Variant] value the contents of the range
|
687
703
|
def []=(name, value)
|
688
|
-
|
689
|
-
workbook.color_if_modified = 42 unless workbook.nil? # aqua-marin
|
690
|
-
set_namevalue_glob(name,value)
|
691
|
-
workbook.color_if_modified = old_color_if_modified
|
704
|
+
set_namevalue_glob(name, value, :color => 42)
|
692
705
|
end
|
693
706
|
|
707
|
+
# @private
|
708
|
+
# returns active workbook
|
709
|
+
def workbook
|
710
|
+
@workbook ||= workbook_class.new(@ole_excel.ActiveWorkbook) if @ole_excel.Workbooks.Count > 0
|
711
|
+
end
|
712
|
+
|
713
|
+
alias_method :active_workbook, :workbook
|
714
|
+
|
694
715
|
# @private
|
695
716
|
def to_s
|
696
717
|
'#<Excel: ' + hwnd.to_s + ('not alive' unless alive?).to_s + '>'
|
@@ -716,15 +737,30 @@ module RobustExcelOle
|
|
716
737
|
self.class.workbook_class
|
717
738
|
end
|
718
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
|
+
|
719
756
|
include MethodHelpers
|
720
757
|
|
721
758
|
private
|
722
759
|
|
723
|
-
# @private
|
724
760
|
def method_missing(name, *args)
|
725
761
|
if name.to_s[0,1] =~ /[A-Z]/
|
726
762
|
raise ObjectNotAlive, 'method missing: Excel not alive' unless alive?
|
727
|
-
if
|
763
|
+
if ::ERRORMESSAGE_JRUBY_BUG
|
728
764
|
begin
|
729
765
|
@ole_excel.send(name, *args)
|
730
766
|
rescue Java::OrgRacobCom::ComFailException => msg
|
@@ -743,7 +779,16 @@ module RobustExcelOle
|
|
743
779
|
end
|
744
780
|
end
|
745
781
|
|
746
|
-
|
782
|
+
public
|
783
|
+
|
784
|
+
# @private
|
785
|
+
class ExcelDamaged < ExcelREOError
|
786
|
+
end
|
787
|
+
|
788
|
+
# @private
|
789
|
+
class UnsavedWorkbooks < ExcelREOError
|
790
|
+
end
|
791
|
+
|
747
792
|
|
748
793
|
Application = Excel
|
749
794
|
|
@@ -752,3 +797,4 @@ end
|
|
752
797
|
class WIN32OLE
|
753
798
|
include Enumerable
|
754
799
|
end
|
800
|
+
|