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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog +47 -4
  3. data/README.rdoc +52 -47
  4. data/___dummy_workbook.xls +0 -0
  5. data/docs/README_excel.rdoc +9 -3
  6. data/docs/README_open.rdoc +86 -44
  7. data/docs/README_ranges.rdoc +90 -81
  8. data/docs/README_save_close.rdoc +9 -9
  9. data/docs/README_sheet.rdoc +17 -17
  10. data/examples/example_ruby_library.rb +27 -0
  11. data/extconf.rb +7 -0
  12. data/lib/robust_excel_ole.rb +7 -4
  13. data/lib/robust_excel_ole/{address.rb → address_tool.rb} +23 -22
  14. data/lib/robust_excel_ole/{reo_common.rb → base.rb} +3 -92
  15. data/lib/robust_excel_ole/bookstore.rb +14 -77
  16. data/lib/robust_excel_ole/cell.rb +30 -18
  17. data/lib/robust_excel_ole/excel.rb +138 -92
  18. data/lib/robust_excel_ole/general.rb +40 -15
  19. data/lib/robust_excel_ole/range.rb +42 -19
  20. data/lib/robust_excel_ole/range_owners.rb +40 -25
  21. data/lib/robust_excel_ole/vba_objects.rb +30 -0
  22. data/lib/robust_excel_ole/version.rb +1 -1
  23. data/lib/robust_excel_ole/workbook.rb +320 -319
  24. data/lib/robust_excel_ole/worksheet.rb +51 -25
  25. data/robust_excel_ole.gemspec +1 -0
  26. data/spec/address_tool_spec.rb +175 -0
  27. data/spec/{reo_common_spec.rb → base_spec.rb} +19 -34
  28. data/spec/bookstore_spec.rb +3 -3
  29. data/spec/cell_spec.rb +67 -25
  30. data/spec/data/more_data/workbook.xls +0 -0
  31. data/spec/excel_spec.rb +137 -369
  32. data/spec/general_spec.rb +21 -27
  33. data/spec/range_spec.rb +57 -3
  34. data/spec/workbook_spec.rb +11 -79
  35. data/spec/workbook_specs/workbook_misc_spec.rb +29 -40
  36. data/spec/workbook_specs/workbook_open_spec.rb +599 -31
  37. data/spec/workbook_specs/workbook_unobtr_spec.rb +760 -93
  38. data/spec/worksheet_spec.rb +36 -4
  39. metadata +12 -7
  40. 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
@@ -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 < RangeOwners
18
- attr_accessor :ole_excel
19
- #attr_accessor :created
20
- attr_accessor :workbook
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 = if JRUBY_BUG_CONNECTEXCEL
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.visible = options[:visible] unless options[:visible].nil?
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
- :visible => @visible || false,
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
- self.visible = opts[:visible]
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 self.current_excel
150
- result = if result.nil?
151
- begin
152
- WIN32OLE.connect('Excel.Application')
153
- rescue
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
- @ole_excel.Visible = @visible = visible_value
555
- @ole_excel.DisplayAlerts = @visible if @displayalerts == :if_visible
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
- @displayalerts = displayalerts_value
561
- @ole_excel.DisplayAlerts = @displayalerts == :if_visible ? @ole_excel.Visible : displayalerts_value
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
- @ole_excel.ScreenUpdating = @screenupdating = screenupdating_value
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
- self.class.new(@ole_excel, options)
642
+ set_options(options)
643
+ #self.class.new(@ole_excel, options)
636
644
  end
637
645
 
638
- def set_options(options)
639
- for_this_instance(options)
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
- old_color_if_modified = workbook.color_if_modified
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 JRUBY_BUG_ERRORMESSAGE
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
- public
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
+