robust_excel_ole 0.3.4 → 0.3.5

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 (49) hide show
  1. data/README.rdoc +73 -26
  2. data/README_detail.rdoc +92 -27
  3. data/examples/edit_sheets/example_access_sheets_and_cells.rb +3 -3
  4. data/examples/edit_sheets/example_concating.rb +12 -12
  5. data/examples/edit_sheets/example_copying.rb +47 -0
  6. data/examples/edit_sheets/example_expanding.rb +17 -26
  7. data/examples/edit_sheets/example_naming.rb +13 -10
  8. data/examples/edit_sheets/example_ranges.rb +2 -2
  9. data/examples/edit_sheets/example_saving.rb +8 -14
  10. data/examples/open_save_close/example_control_to_excel.rb +1 -1
  11. data/examples/open_save_close/example_if_obstructed_closeifsaved.rb +3 -3
  12. data/examples/open_save_close/example_if_obstructed_save.rb +3 -3
  13. data/examples/open_save_close/example_if_unsaved_accept.rb +1 -1
  14. data/examples/open_save_close/example_if_unsaved_forget.rb +4 -4
  15. data/examples/open_save_close/example_if_unsaved_forget_more.rb +5 -5
  16. data/examples/open_save_close/example_read_only.rb +1 -1
  17. data/examples/open_save_close/example_rename_cells.rb +1 -13
  18. data/examples/open_save_close/example_simple.rb +1 -1
  19. data/examples/open_save_close/example_unobtrusively.rb +3 -3
  20. data/lib/robust_excel_ole.rb +81 -2
  21. data/lib/robust_excel_ole/book.rb +171 -118
  22. data/lib/robust_excel_ole/{book_store.rb → bookstore.rb} +2 -2
  23. data/lib/robust_excel_ole/excel.rb +153 -24
  24. data/lib/robust_excel_ole/range.rb +2 -2
  25. data/lib/robust_excel_ole/sheet.rb +84 -35
  26. data/lib/robust_excel_ole/version.rb +1 -1
  27. data/reo.bat +3 -0
  28. data/spec/book_close_spec.rb +179 -0
  29. data/spec/book_misc_spec.rb +365 -0
  30. data/spec/book_open_spec.rb +793 -0
  31. data/spec/book_save_spec.rb +257 -0
  32. data/spec/book_sheet_spec.rb +160 -0
  33. data/spec/book_spec.rb +145 -1533
  34. data/spec/book_subclass_spec.rb +50 -0
  35. data/spec/book_unobtr_spec.rb +950 -0
  36. data/spec/{book_store_spec.rb → bookstore_spec.rb} +5 -5
  37. data/spec/cell_spec.rb +6 -6
  38. data/spec/data/{more_workbook.xls → another_workbook.xls} +0 -0
  39. data/spec/data/different_workbook.xls +0 -0
  40. data/spec/data/workbook.xls +0 -0
  41. data/spec/data/workbook.xlsm +0 -0
  42. data/spec/data/workbook.xlsx +0 -0
  43. data/spec/data/workbook_linked.xlsm +0 -0
  44. data/spec/data/workbook_linked_sub.xlsm +0 -0
  45. data/spec/excel_spec.rb +204 -5
  46. data/spec/range_spec.rb +6 -6
  47. data/spec/sheet_spec.rb +122 -34
  48. metadata +18 -8
  49. data/spec/data/workbook_connected_sub.xlsm +0 -0
@@ -3,7 +3,7 @@
3
3
 
4
4
  module RobustExcelOle
5
5
 
6
- class BookStore
6
+ class Bookstore
7
7
 
8
8
  def initialize
9
9
  @filename2books = Hash.new {|hash, key| hash[key] = [] }
@@ -99,7 +99,7 @@ module RobustExcelOle
99
99
 
100
100
  end
101
101
 
102
- class BookStoreError < WIN32OLERuntimeError # :nodoc: #
102
+ class BookstoreError < WIN32OLERuntimeError # :nodoc: #
103
103
  end
104
104
 
105
105
  end
@@ -6,16 +6,6 @@ module RobustExcelOle
6
6
 
7
7
  @@hwnd2excel = {}
8
8
 
9
- # closes all Excel instances
10
- def self.close_all
11
- while current_excel do
12
- close_one_excel
13
- GC.start
14
- sleep 0.3
15
- #free_all_ole_objects
16
- end
17
- end
18
-
19
9
  # creates a new Excel instance
20
10
  def self.create
21
11
  new(:reuse => false)
@@ -55,7 +45,7 @@ module RobustExcelOle
55
45
  result = stored
56
46
  else
57
47
  result = super(options)
58
- result.instance_variable_set(:@excel, excel)
48
+ result.instance_variable_set(:@this_excel, excel)
59
49
  WIN32OLE.const_load(excel, RobustExcelOle) unless RobustExcelOle.const_defined?(:CONSTANTS)
60
50
  @@hwnd2excel[hwnd] = result
61
51
  end
@@ -63,6 +53,107 @@ module RobustExcelOle
63
53
  end
64
54
 
65
55
  def initialize(options= {}) # :nodoc: #
56
+ @excel = self
57
+ end
58
+
59
+ # closes all Excel instances
60
+ # options:
61
+ # :if_unsaved if unsaved workbooks are open in an Excel instance
62
+ # :raise (default) -> raise an exception
63
+ # :save -> save the workbooks before closing
64
+ # :forget -> close the excel instance without saving the workbooks
65
+ # :alert -> give control to Excel
66
+ # :hard kill the Excel instances hard (default: false)
67
+ def self.close_all(options={})
68
+ options = {
69
+ :if_unsaved => :raise,
70
+ :hard => false
71
+ }.merge(options)
72
+ while current_excel do
73
+ #current_excel.close(options)
74
+ close_one_excel
75
+ GC.start
76
+ sleep 0.3
77
+ # free_all_ole_objects if options[:hard] ???
78
+ end
79
+ end
80
+
81
+ # close the Excel
82
+ # :if_unsaved if unsaved workbooks are open in an Excel instance
83
+ # :raise (default) -> raise an exception
84
+ # :save -> save the workbooks before closing
85
+ # :forget -> close the excel instance without saving the workbooks
86
+ # :alert -> give control to Excel
87
+ # :hard kill the Excel instance hard (default: false)
88
+ def close(options = {})
89
+ options = {
90
+ :if_unsaved => :raise,
91
+ :hard => false
92
+ }.merge(options)
93
+ unsaved_books = self.unsaved_workbooks
94
+ unless unsaved_books.empty?
95
+ case options[:if_unsaved]
96
+ when :raise
97
+ raise ExcelErrorClose, "Excel contains unsaved workbooks"
98
+ when :save
99
+ unsaved_workbooks.each do |workbook|
100
+ workbook.Save
101
+ end
102
+ close_excel(:hard => options[:hard])
103
+ when :forget
104
+ close_excel(:hard => options[:hard])
105
+ when :alert
106
+ with_displayalerts true do
107
+ unsaved_workbooks.each do |workbook|
108
+ workbook.Save
109
+ end
110
+ close_excel(:hard => options[:hard])
111
+ end
112
+ else
113
+ raise ExcelErrorClose, ":if_unsaved: invalid option: #{options[:if_unsaved]}"
114
+ end
115
+ else
116
+ close_excel(:hard => options[:hard])
117
+ end
118
+ raise ExcelUserCanceled, "close: canceled by user" if options[:if_unsaved] == :alert && self.unsaved_workbooks
119
+ end
120
+
121
+ private
122
+
123
+ def close_excel(options)
124
+ excel = @this_excel
125
+ excel.Workbooks.Close
126
+ excel_hwnd = excel.HWnd
127
+ excel.Quit
128
+ weak_excel_ref = WeakRef.new(excel)
129
+ excel = nil
130
+ GC.start
131
+ sleep 0.2
132
+ if weak_excel_ref.weakref_alive? then
133
+ #if WIN32OLE.ole_reference_count(weak_xlapp) > 0
134
+ begin
135
+ weak_excel_ref.ole_free
136
+ puts "successfully ole_freed #{weak_excel_ref}"
137
+ rescue
138
+ puts "could not do ole_free on #{weak_excel_ref}"
139
+ end
140
+ end
141
+ hwnd2excel(excel_hwnd).die rescue nil
142
+ #@@hwnd2excel[excel_hwnd] = nil
143
+ #Excel.free_all_ole_objects
144
+ if options[:hard] then
145
+ process_id = Win32API.new("user32", "GetWindowThreadProcessId", ["I","P"], "I")
146
+ pid_puffer = " " * 32
147
+ process_id.call(excel_hwnd, pid_puffer)
148
+ pid = pid_puffer.unpack("L")[0]
149
+ Process.kill("KILL", pid)
150
+ end
151
+ end
152
+
153
+ public
154
+
155
+ def excel
156
+ self
66
157
  end
67
158
 
68
159
  # generate, save and close an empty workbook
@@ -100,51 +191,86 @@ module RobustExcelOle
100
191
 
101
192
  # returns true, if the Excel instances responds to VVA methods, false otherwise
102
193
  def alive?
103
- @excel.Name
194
+ @this_excel.Name
104
195
  true
105
196
  rescue
106
- puts $!.message
197
+ #puts $!.message
107
198
  false
108
199
  end
109
200
 
201
+ def print_workbooks
202
+ self.Workbooks.each {|w| puts w.Name}
203
+ end
204
+
205
+ def unsaved_workbooks
206
+ result = []
207
+ begin
208
+ self.Workbooks.each {|w| result << w unless (w.Saved || w.ReadOnly)}
209
+ rescue RuntimeError => msg
210
+ puts "RuntimeError: #{msg.message}"
211
+ raise ExcelErrorOpen, "Excel instance not alive or damaged" if msg.message =~ /failed to get Dispatch Interface/
212
+ end
213
+ result
214
+ end
215
+ # yields different WIN32OLE objects than book.workbook
216
+ #self.class.extend Enumerable
217
+ #self.class.map {|w| (not w.Saved)}
218
+
110
219
  # set DisplayAlerts in a block
111
220
  def with_displayalerts displayalerts_value
112
- old_displayalerts = @excel.DisplayAlerts
113
- @excel.DisplayAlerts = displayalerts_value
221
+ old_displayalerts = @this_excel.DisplayAlerts
222
+ @this_excel.DisplayAlerts = displayalerts_value
114
223
  begin
115
224
  yield self
116
225
  ensure
117
- @excel.DisplayAlerts = old_displayalerts
226
+ @this_excel.DisplayAlerts = old_displayalerts
118
227
  end
119
228
  end
120
229
 
121
230
  # enable DisplayAlerts in the current Excel instance
122
231
  def displayalerts= displayalerts_value
123
- @excel.DisplayAlerts = displayalerts_value
232
+ @this_excel.DisplayAlerts = displayalerts_value
124
233
  end
125
234
 
126
235
  # return if in the current Excel instance DisplayAlerts is enabled
127
236
  def displayalerts
128
- @excel.DisplayAlerts
237
+ @this_excel.DisplayAlerts
129
238
  end
130
239
 
131
240
  # make the current Excel instance visible or invisible
132
241
  def visible= visible_value
133
- @excel.Visible = visible_value
242
+ @this_excel.Visible = visible_value
134
243
  end
135
244
 
136
245
  # return if the current Excel instance is visible
137
246
  def visible
138
- @excel.Visible
247
+ @this_excel.Visible
139
248
  end
140
249
 
250
+ def to_s
251
+ "#<" + "EXCEL:#{hwnd_xxxx}" + ("#{"not alive" unless self.alive?}") + ">"
252
+ end
253
+
254
+ def inspect
255
+ self.to_s
256
+ end
141
257
 
142
258
  private
143
259
 
144
260
  # closes one Excel instance
145
- def self.close_one_excel
261
+ def self.close_one_excel(options={})
146
262
  excel = current_excel
147
263
  if excel then
264
+ weak_ole_excel = WeakRef.new(excel)
265
+ excel = nil
266
+ close_excel_ole_instance(weak_ole_excel.__getobj__)
267
+ end
268
+ end
269
+
270
+ def self.close_excel_ole_instance(ole_excel)
271
+ excel = ole_excel
272
+ ole_excel = nil
273
+ begin
148
274
  excel.Workbooks.Close
149
275
  excel_hwnd = excel.HWnd
150
276
  excel.Quit
@@ -165,6 +291,10 @@ module RobustExcelOle
165
291
 
166
292
  hwnd2excel(excel_hwnd).die rescue nil
167
293
  #@@hwnd2excel[excel_hwnd] = nil
294
+
295
+ rescue => e
296
+ puts "Error when closing Excel: " + e.message
297
+ #puts e.backtrace
168
298
  end
169
299
 
170
300
 
@@ -220,14 +350,13 @@ module RobustExcelOle
220
350
 
221
351
  # set this Excel instance to nil
222
352
  def die
223
- @excel = nil
353
+ @this_excel = nil
224
354
  end
225
355
 
226
-
227
356
  def method_missing(name, *args)
228
357
  if name.to_s[0,1] =~ /[A-Z]/
229
358
  begin
230
- @excel.send(name, *args)
359
+ @this_excel.send(name, *args)
231
360
  rescue WIN32OLERuntimeError => msg
232
361
  if msg.message =~ /unknown property or method/
233
362
  raise VBAMethodMissingError, "unknown VBA property or method #{name}"
@@ -27,8 +27,8 @@ module RobustExcelOle
27
27
  end
28
28
 
29
29
  def [] index
30
- @cells ||= []
31
- @cells[index + 1] ||= RobustExcelOle::Cell.new(@range.Cells.Item(index + 1))
30
+ @cells = []
31
+ @cells[index + 1] = RobustExcelOle::Cell.new(@range.Cells.Item(index + 1))
32
32
  end
33
33
 
34
34
  def method_missing(id, *args) # :nodoc: #
@@ -1,16 +1,16 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  module RobustExcelOle
3
3
  class Sheet
4
- attr_reader :sheet
4
+ attr_reader :worksheet
5
5
  include Enumerable
6
6
 
7
7
  def initialize(win32_worksheet)
8
- @sheet = win32_worksheet
9
- if @sheet.ProtectContents
10
- @sheet.Unprotect
8
+ @worksheet = win32_worksheet
9
+ if @worksheet.ProtectContents
10
+ @worksheet.Unprotect
11
11
  @end_row = last_row
12
12
  @end_column = last_column
13
- @sheet.Protect
13
+ @worksheet.Protect
14
14
  else
15
15
  @end_row = last_row
16
16
  @end_column = last_column
@@ -18,21 +18,44 @@ module RobustExcelOle
18
18
  end
19
19
 
20
20
  def name
21
- @sheet.Name
21
+ @worksheet.Name
22
22
  end
23
23
 
24
24
  def name= (new_name)
25
- @sheet.Name = new_name
25
+ begin
26
+ @worksheet.Name = new_name
27
+ rescue WIN32OLERuntimeError => msg
28
+ if msg.message =~ /800A03EC/
29
+ raise ExcelErrorSheet, "sheet name already exists"
30
+ else
31
+ puts "#{msg.message}"
32
+ raise ExcelErrorSheetUnknown
33
+ end
34
+ end
26
35
  end
27
36
 
28
- def [] y, x
29
- yx = "#{y+1}_#{x+1}"
30
- @cells ||= { }
31
- @cells[yx] ||= RobustExcelOle::Cell.new(@sheet.Cells.Item(y+1, x+1))
37
+ # return the value of a cell, if row and column, or its name are given
38
+ def [] p1, p2 = :__not_provided
39
+ if p2 != :__not_provided
40
+ y, x = p1, p2
41
+ yx = "#{y}_#{x}"
42
+ @cells = { }
43
+ @cells[yx] = RobustExcelOle::Cell.new(@worksheet.Cells.Item(y, x))
44
+ else
45
+ name = p1
46
+ nvalue(name)
47
+ end
32
48
  end
33
49
 
34
- def []= (y, x, value)
35
- @sheet.Cells.Item(y+1, x+1).Value = value
50
+ # set the value of a cell, if row and column, or its name are given
51
+ def []= (p1, p2, p3 = :__not_provided)
52
+ if p3 != :__not_provided
53
+ y, x, value = p1, p2, p3
54
+ @worksheet.Cells.Item(y, x).Value = value
55
+ else
56
+ name, value = p1, p2
57
+ set_nvalue(name, value)
58
+ end
36
59
  end
37
60
 
38
61
  def each
@@ -47,7 +70,7 @@ module RobustExcelOle
47
70
  offset += 1
48
71
  1.upto(@end_row) do |row|
49
72
  next if row < offset
50
- yield RobustExcelOle::Range.new(@sheet.Range(@sheet.Cells(row, 1), @sheet.Cells(row, @end_column)))
73
+ yield RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(row, 1), @worksheet.Cells(row, @end_column)))
51
74
  end
52
75
  end
53
76
 
@@ -61,7 +84,7 @@ module RobustExcelOle
61
84
  offset += 1
62
85
  1.upto(@end_column) do |column|
63
86
  next if column < offset
64
- yield RobustExcelOle::Range.new(@sheet.Range(@sheet.Cells(1, column), @sheet.Cells(@end_row, column)))
87
+ yield RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(1, column), @worksheet.Cells(@end_row, column)))
65
88
  end
66
89
  end
67
90
 
@@ -72,49 +95,78 @@ module RobustExcelOle
72
95
  end
73
96
 
74
97
  def row_range(row, range = nil)
75
- range ||= 0..@end_column - 1
76
- RobustExcelOle::Range.new(@sheet.Range(@sheet.Cells(row + 1, range.min + 1), @sheet.Cells(row + 1, range.max + 1)))
98
+ range ||= 1..@end_column
99
+ RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(row , range.min ), @worksheet.Cells(row , range.max )))
77
100
  end
78
101
 
79
102
  def col_range(col, range = nil)
80
- range ||= 0..@end_row - 1
81
- RobustExcelOle::Range.new(@sheet.Range(@sheet.Cells(range.min + 1, col + 1), @sheet.Cells(range.max + 1, col + 1)))
103
+ range ||= 1..@end_row
104
+ RobustExcelOle::Range.new(@worksheet.Range(@worksheet.Cells(range.min , col ), @worksheet.Cells(range.max , col )))
82
105
  end
83
106
 
84
- # returns the contents of a range or cell with given name
85
- def nvalue(name)
107
+ # returns the contents of a range with given name
108
+ # if no contents could returned, then return default value, if a default value was provided
109
+ # raise an error, otherwise
110
+ def nvalue(name, opts = {:default => nil})
86
111
  begin
87
112
  item = self.Names.Item(name)
88
113
  rescue WIN32OLERuntimeError
89
- raise SheetErrorNValue, "name #{name} not in sheet"
114
+ return opts[:default] if opts[:default]
115
+ raise SheetError, "name #{name} not in sheet"
90
116
  end
91
117
  begin
92
- referstorange = item.RefersToRange
93
- rescue WIN32OLERuntimeError
94
- raise SheetErrorNValue, "range error in sheet"
118
+ value = item.RefersToRange.Value
119
+ rescue WIN32OLERuntimeError
120
+ return opts[:default] if opts[:default]
121
+ raise SheetError, "RefersToRange of name #{name}"
95
122
  end
123
+ value
124
+ end
125
+
126
+ def set_nvalue(name,value)
96
127
  begin
97
- value = referstorange.Value
128
+ item = self.Names.Item(name)
98
129
  rescue WIN32OLERuntimeError
99
- raise SheetErrorNValue, "value error in sheet"
130
+ raise SheetError, "name #{name} not in sheet"
131
+ end
132
+ begin
133
+ item.RefersToRange.Value = value
134
+ rescue WIN32OLERuntimeError
135
+ raise SheetError, "RefersToRange of name #{name}"
136
+ end
137
+ end
138
+
139
+ # assigns a name to a range (a cell) given by an address
140
+ def set_name(name,row,column)
141
+ begin
142
+ old_name = self[row,column].Name.Name rescue nil
143
+ if old_name
144
+ self[row,column].Name.Name = name
145
+ else
146
+ address = "Z" + row.to_s + "S" + column.to_s
147
+ self.Names.Add("Name" => name, "RefersToR1C1" => "=" + address)
148
+ end
149
+ rescue WIN32OLERuntimeError => msg
150
+ puts "WIN32OLERuntimeError: #{msg.message}"
151
+ raise SheetError, "cannot add name #{name} to cell with row #{row} and column #{column}"
100
152
  end
101
153
  end
102
154
 
103
155
  def method_missing(id, *args) # :nodoc: #
104
- @sheet.send(id, *args)
156
+ @worksheet.send(id, *args)
105
157
  end
106
158
 
107
159
  private
108
160
  def last_row
109
- special_last_row = @sheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Row
110
- used_last_row = @sheet.UsedRange.Rows.Count
161
+ special_last_row = @worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Row
162
+ used_last_row = @worksheet.UsedRange.Rows.Count
111
163
 
112
164
  special_last_row >= used_last_row ? special_last_row : used_last_row
113
165
  end
114
166
 
115
167
  def last_column
116
- special_last_column = @sheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Column
117
- used_last_column = @sheet.UsedRange.Columns.Count
168
+ special_last_column = @worksheet.UsedRange.SpecialCells(RobustExcelOle::XlLastCell).Column
169
+ used_last_column = @worksheet.UsedRange.Columns.Count
118
170
 
119
171
  special_last_column >= used_last_column ? special_last_column : used_last_column
120
172
  end
@@ -125,7 +177,4 @@ module RobustExcelOle
125
177
  class SheetError < RuntimeError # :nodoc: #
126
178
  end
127
179
 
128
- class SheetErrorNValue < WIN32OLERuntimeError # :nodoc: #
129
- end
130
-
131
180
  end