robust_excel_ole 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
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