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
@@ -6,30 +6,30 @@ Worksheets are represented by Worksheet objects.
6
6
 
7
7
  Assume you have opened a workbook
8
8
 
9
- book = Workbook.open('spec/data/workbook.xls', :visible => true)
9
+ workbook = Workbook.open('spec/data/workbook.xls', :visible => true)
10
10
 
11
11
  === Accessing a worksheet.
12
12
 
13
13
  You can access a worksheet by giving the number
14
14
 
15
- sheet = book.sheet(1)
15
+ worksheet = book.sheet(1)
16
16
 
17
17
  or its name
18
18
 
19
- sheet = book.sheet('Sheet1')
19
+ worksheet = book.sheet('Sheet1')
20
20
 
21
21
  You can get the first and last worksheet with
22
22
 
23
- sheet = book.first_sheet
23
+ worksheet = book.first_sheet
24
24
 
25
25
  and
26
26
 
27
- sheet = book.last_sheet
27
+ worksheet = book.last_sheet
28
28
 
29
- You can access all Sheet objects by using the methods Workbook#each.
29
+ You can access all Worksheet objects by using the methods Workbook#each.
30
30
 
31
- book.each do |sheet|
32
- # do something with sheet
31
+ workbook.each do |worksheet|
32
+ # do something with worksheet
33
33
  end
34
34
 
35
35
  Once you have got a Sheet object (in RobustExcelOle), you can apply all VBA methods that you would apply to a VBA Worksheet object
@@ -40,38 +40,38 @@ For some common and complex tasks you can apply methods of RobustExcelOle.
40
40
 
41
41
  You can read and change the worksheet name.
42
42
 
43
- sheet1.name
43
+ worksheet1.name
44
44
  # => "Sheet1"
45
45
 
46
- sheet1.name = "new_sheet"
46
+ worksheet1.name = "new_sheet"
47
47
 
48
48
  === Adding and copying a worksheet.
49
49
 
50
50
  You can add (append) an empty worksheet using
51
51
 
52
- book.add_empty_sheet
52
+ workbook.add_empty_sheet
53
53
 
54
54
  Additionally you can name it.
55
55
 
56
- book.add_empty_sheet(:as => 'sheet_name')
56
+ workbook.add_empty_sheet(:as => 'sheet_name')
57
57
 
58
58
  You can specify the position of the added empty worksheet.
59
59
 
60
- book.add_empty_sheet(:as => 'new_name', :before => another_sheet)
60
+ workbook.add_empty_sheet(:as => 'new_name', :before => another_sheet)
61
61
 
62
62
  You can copy a worksheet and add it.
63
63
 
64
- book.copy_sheet sheet
64
+ workbook.copy_sheet sheet
65
65
 
66
66
  Additionally you can specify a name and a position.
67
67
 
68
- book.copy_sheet(sheet, :as => 'new_name', :after => another_sheet)
68
+ workbook.copy_sheet(sheet, :as => 'new_name', :after => another_sheet)
69
69
 
70
70
  If you want to copy a worksheet, if a worksheet +sheet+ is given, and add an empty worksheet, if no worksheet is given, then use
71
71
 
72
- book.add_or_copy_sheet
72
+ workbook.add_or_copy_sheet
73
73
 
74
- book.add_or_copy_sheet(sheet, :as => 'new_name', :after => another_sheet)
74
+ workbook.add_or_copy_sheet(sheet, :as => 'new_name', :after => another_sheet)
75
75
 
76
76
  Note, that running in jruby, due to some restrictions of jruby, there is a workaround when adding or copy a worksheet at the end (appending): the last worksheet is being copied and deleted afterwards, in order to serve as a dummy worksheet. This may cause a different behaviour.
77
77
 
@@ -0,0 +1,27 @@
1
+ #require 'robust_excel_ole'
2
+
3
+ #workbook = Workbook.open './sample_excel_files/xlsx_500_rows.xlsx'
4
+
5
+ require_relative '../lib/robust_excel_ole'
6
+
7
+ include RobustExcelOle
8
+
9
+ workbook = Workbook.open './../spec/data/workbook.xls'
10
+
11
+ puts "Found #{workbook.worksheets_count} worksheets"
12
+
13
+ workbook.each do |worksheet|
14
+ puts "Reading: #{worksheet.name}"
15
+ num_rows = 0
16
+
17
+ worksheet.each do |row|
18
+ row_cells = row.map{ |cell| cell.value }
19
+ num_rows += 1
20
+
21
+ # uncomment to print out row values
22
+ # puts row_cells.join " "
23
+ end
24
+ puts "Read #{num_rows} rows"
25
+ end
26
+
27
+ puts 'Done'
@@ -0,0 +1,7 @@
1
+ dummy_make_content = "make:\n" \
2
+ "\t:\n" \
3
+ "install:\n" \
4
+ "\t:\n" \
5
+ "clean:\n" \
6
+ "\t:\n"
7
+ File.write('Makefile', dummy_make_content)
@@ -3,10 +3,11 @@ if RUBY_PLATFORM =~ /java/
3
3
  else
4
4
  require 'win32ole'
5
5
  end
6
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/reo_common')
7
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/range_owners')
8
- require File.join(File.dirname(__FILE__), 'robust_excel_ole/address')
6
+ require File.join(File.dirname(__FILE__), 'robust_excel_ole/base')
9
7
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/general')
8
+ require File.join(File.dirname(__FILE__), 'robust_excel_ole/vba_objects')
9
+ require File.join(File.dirname(__FILE__), 'robust_excel_ole/range_owners')
10
+ require File.join(File.dirname(__FILE__), 'robust_excel_ole/address_tool')
10
11
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/excel')
11
12
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/bookstore')
12
13
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/workbook')
@@ -14,5 +15,7 @@ require File.join(File.dirname(__FILE__), 'robust_excel_ole/worksheet')
14
15
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/cell')
15
16
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/range')
16
17
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/cygwin') if RUBY_PLATFORM =~ /cygwin/
17
- #+#require "robust_excel_ole/version"
18
18
  require File.join(File.dirname(__FILE__), 'robust_excel_ole/version')
19
+
20
+ include RobustExcelOle
21
+ include General
@@ -2,37 +2,38 @@
2
2
 
3
3
  module RobustExcelOle
4
4
 
5
- class Address < REOCommon
5
+ class AddressTool < Base
6
6
 
7
- def self.new(r1c1_letters)
8
- @@row_letter = r1c1_letters[0..0]
9
- @@col_letter = r1c1_letters[1..1]
7
+ def initialize(address_string)
8
+ r1c1_letters = address_string.gsub(/[0-9]/,'')
9
+ @row_letter = r1c1_letters[0..0]
10
+ @col_letter = r1c1_letters[1..1]
10
11
  end
11
12
 
12
13
  # address formats that are valid:
13
14
  # r1c1-format: e.g. "Z3S1", "Z3S1:Z5S2", "Z[3]S1", "Z3S[-1]:Z[5]S1", "Z[3]", "S[-2]"
14
15
  # infinite ranges are not possible, e.g. "Z3:Z5", "S2:S5", "Z2", "S3", "Z[2]"
15
- # int_range: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
16
- # [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
16
+ # integer_ranges-fromat: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
17
+ # [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
17
18
  # a1-format: e.g. "A3", "A3:B5", "A:B", "3:5", "A", "3"
18
19
 
19
- def self.r1c1(address)
20
+ def as_r1c1(address)
20
21
  transform_address(address,:r1c1)
21
22
  end
22
23
 
23
- def self.a1(address)
24
+ def as_a1(address)
24
25
  transform_address(address,:a1)
25
26
  end
26
27
 
27
28
  # valid address formats: e.g. [3,1], [3,"A"], [3..5,1..2], [3..5, "A".."B"],
28
29
  # [3..4, nil], [nil, 2..4], [2,nil], [nil,4]
29
- def self.int_range(address)
30
+ def as_integer_ranges(address)
30
31
  transform_address(address,:int_range)
31
32
  end
32
33
 
33
34
  private
34
35
 
35
- def self.transform_address(address, format)
36
+ def transform_address(address, format)
36
37
  address = address.is_a?(Array) ? address : [address]
37
38
  raise AddressInvalid, "address #{address.inspect} has more than two components" if address.size > 2
38
39
  begin
@@ -70,8 +71,8 @@ module RobustExcelOle
70
71
  raise AddressInvalid, "address (#{address.inspect}) format not correct"
71
72
  end
72
73
  if format==:r1c1
73
- r1c1_string(@@row_letter,rows,:min) + r1c1_string(@@col_letter,columns,:min) + ":" +
74
- r1c1_string(@@row_letter,rows,:max) + r1c1_string(@@col_letter,columns,:max)
74
+ r1c1_string(@row_letter,rows,:min) + r1c1_string(@col_letter,columns,:min) + ":" +
75
+ r1c1_string(@row_letter,rows,:max) + r1c1_string(@col_letter,columns,:max)
75
76
  elsif format==:int_range
76
77
  [rows,columns]
77
78
  else
@@ -79,8 +80,7 @@ module RobustExcelOle
79
80
  end
80
81
  end
81
82
 
82
- # @private
83
- def self.r1c1_string(letter,int_range,type)
83
+ def r1c1_string(letter,int_range,type)
84
84
  return "" if int_range.nil? || int_range.begin.nil?
85
85
  parameter = type == :min ? int_range.begin : int_range.end
86
86
  is_relative = parameter.is_a?(Array)
@@ -88,27 +88,28 @@ module RobustExcelOle
88
88
  letter + (is_relative ? "(" : "") + parameter.to_s + (is_relative ? ")" : "")
89
89
  end
90
90
 
91
- # @private
92
- def self.analyze(comp,format)
91
+ def analyze(comp,format)
93
92
  row_comp, col_comp = if format==:a1
94
93
  [comp.gsub(/[A-Z]/,''), comp.gsub(/[0-9]/,'')]
95
94
  else
96
- a,b = comp.split(@@row_letter)
97
- c,d = b.split(@@col_letter)
95
+ a,b = comp.split(@row_letter)
96
+ c,d = b.split(@col_letter)
98
97
  b.nil? ? ["",b] : (d.nil? ? [c,""] : [c,d])
99
98
  end
100
- def self.s2n(s)
99
+ def s2n(s)
101
100
  s!="" ? (s[0] == "[" ? [s.gsub(/\[|\]/,'').to_i] : (s.to_i!=0 ? s.to_i : s)) : nil
102
101
  end
103
102
  [s2n(row_comp), s2n(col_comp)]
104
103
  end
105
104
 
106
-
107
- # @private
108
- def self.str2num(str)
105
+ def str2num(str)
109
106
  str.tr("A-Z","0-9A-P").to_i(26) + (26**str.size-1)/25
110
107
  end
111
108
 
112
109
  end
113
110
 
111
+ # @private
112
+ class AddressInvalid < REOError
113
+ end
114
+
114
115
  end
@@ -50,94 +50,10 @@ module RobustExcelOle
50
50
  class MiscREOError < REOError
51
51
  end
52
52
 
53
- # @private
54
- class ExcelDamaged < ExcelREOError
55
- end
56
-
57
- # @private
58
- class UnsavedWorkbooks < ExcelREOError
59
- end
60
-
61
- # @private
62
- class WorkbookBlocked < WorkbookREOError
63
- end
64
-
65
- # @private
66
- class WorkbookNotSaved < WorkbookREOError
67
- end
68
-
69
- # @private
70
- class WorkbookReadOnly < WorkbookREOError
71
- end
72
-
73
- # @private
74
- class WorkbookBeingUsed < WorkbookREOError
75
- end
76
-
77
- # @private
78
- class WorkbookConnectingUnsavedError < WorkbookREOError
79
- end
80
-
81
- # @private
82
- class WorkbookConnectingBlockingError < WorkbookREOError
83
- end
84
-
85
- # @private
86
- class WorkbookConnectingUnknownError < WorkbookREOError
87
- end
88
-
89
- # @private
90
- class FileNotFound < FileREOError
91
- end
92
-
93
- # @private
94
- class FileNameNotGiven < FileREOError
95
- end
96
-
97
- # @private
98
- class FileAlreadyExists < FileREOError
99
- end
100
-
101
- # @private
102
- class NameNotFound < NamesREOError
103
- end
104
-
105
- # @private
106
- class NameAlreadyExists < NamesREOError
107
- end
108
-
109
- # @private
110
- class RangeNotEvaluatable < MiscREOError
111
- end
112
-
113
- # @private
114
- class RangeNotCreated < MiscREOError
115
- end
116
-
117
- # @private
118
- class RangeNotCopied < MiscREOError
119
- end
120
-
121
- # @private
122
- class OptionInvalid < MiscREOError
123
- end
124
-
125
- # @private
126
- class ObjectNotAlive < MiscREOError
127
- end
128
-
129
53
  # @private
130
54
  class TypeREOError < REOError
131
55
  end
132
56
 
133
- # @private
134
- class TimeOut < REOError
135
- end
136
-
137
- # @private
138
- class AddressInvalid < REOError
139
- end
140
-
141
57
  # @private
142
58
  class UnexpectedREOError < REOError
143
59
  end
@@ -145,13 +61,9 @@ module RobustExcelOle
145
61
  # @private
146
62
  class NotImplementedREOError < REOError
147
63
  end
64
+
148
65
 
149
- class REOCommon
150
-
151
- # @private
152
- def excel
153
- raise TypeREOError, 'receiver instance is neither an Excel nor a Workbook'
154
- end
66
+ class Base
155
67
 
156
68
  # @private
157
69
  def own_methods
@@ -173,8 +85,7 @@ module RobustExcelOle
173
85
  home = homes.find { |h| !ENV[h].nil? }
174
86
  reo_log_dir = ENV[home]
175
87
  else
176
- #reo_log_dir = REO_LOG_DIR
177
- reo_log_dir = "C:/Users/User"
88
+ reo_log_dir = REO_LOG_DIR
178
89
  end
179
90
  File.open(reo_log_dir + '/' + REO_LOG_FILE,'a') do |file|
180
91
  file.puts text
@@ -2,7 +2,8 @@
2
2
 
3
3
  module RobustExcelOle
4
4
 
5
- class Bookstore < REOCommon
5
+ class Bookstore < Base
6
+
6
7
  def initialize
7
8
  @filename2books ||= Hash.new { |hash, key| hash[key] = [] }
8
9
  @hidden_excel_instance = nil
@@ -29,11 +30,9 @@ module RobustExcelOle
29
30
  # otherwise proceeds according to prefer_writable
30
31
  def fetch(filename, options = { :prefer_writable => true })
31
32
  return nil unless filename
32
-
33
33
  filename = General.absolute_path(filename)
34
34
  filename_key = General.canonize(filename)
35
- weakref_books = @filename2books[filename_key]
36
- weakref_books = consider_networkpaths(filename_key) if weakref_books.empty? || weakref_books.nil?
35
+ weakref_books = @filename2books[filename_key]
37
36
  return nil if weakref_books.nil? || weakref_books.empty?
38
37
 
39
38
  result = open_book = closed_book = nil
@@ -67,68 +66,6 @@ module RobustExcelOle
67
66
  result
68
67
  end
69
68
 
70
- # @private
71
- def consider_networkpaths(filename)
72
- network = WIN32OLE.new('WScript.Network')
73
- drives = network.enumnetworkdrives
74
- drive_letter, filename_after_drive_letter = filename.split(':')
75
- found_filename = nil
76
- # if filename starts with a drive letter not c and this drive exists,
77
- # then if there is the corresponding host_share_path in the bookstore,
78
- # then take the corresponding workbooks
79
- if drive_letter != 'c' && drive_letter != filename
80
- for i in 0 .. drives.Count-1
81
- next if i % 2 == 1
82
- if drives.Item(i).gsub(':','').downcase == drive_letter
83
- hostname_share = drives.Item(i+1).gsub('\\','/').gsub('//','').downcase
84
- break
85
- end
86
- end
87
- @filename2books.each do |stored_filename,_|
88
- if hostname_share && stored_filename
89
- if stored_filename[0] == '/'
90
- index_hostname = stored_filename[1,stored_filename.length].index('/')+2
91
- index_hostname_share = stored_filename[index_hostname,stored_filename.length].index('/')
92
- hostname_share_in_stored_filename = stored_filename[1,index_hostname+index_hostname_share-1]
93
- if hostname_share_in_stored_filename == hostname_share
94
- found_filename = stored_filename
95
- break
96
- end
97
- end
98
- end
99
- end
100
- elsif filename[0] == '/'
101
- # if filename starts with a host name and share, and this is an existing host name share path,
102
- # then if there are workbooks with the corresponding drive letter,
103
- # then take these workbooks,
104
- index_hostname = filename[1,filename.length].index('/')+2
105
- index_hostname_share = filename[index_hostname,filename.length].index('/')
106
- hostname_share_in_filename = filename[1,index_hostname+index_hostname_share-1]
107
- filename_after_hostname_share = filename[index_hostname+index_hostname_share+1, filename.length]
108
- require 'socket'
109
- hostname = Socket.gethostname
110
- if hostname_share_in_filename[0,hostname_share_in_filename.index('/')] == hostname.downcase
111
- for i in 0 .. drives.Count-1
112
- next if i % 2 == 1
113
- hostname_share = drives.Item(i+1).gsub('\\','/').gsub('//','').downcase
114
- if hostname_share == hostname_share_in_filename
115
- drive_letter = drives.Item(i).gsub(':','').downcase
116
- break
117
- end
118
- end
119
- @filename2books.each do |stored_filename,_|
120
- if stored_filename
121
- if drive_letter && stored_filename.start_with?(drive_letter.downcase) && stored_filename.end_with?(filename_after_hostname_share)
122
- found_filename = stored_filename
123
- break
124
- end
125
- end
126
- end
127
- end
128
- end
129
- @filename2books[found_filename]
130
- end
131
-
132
69
  # stores a workbook
133
70
  # @param [Workbook] book a given book
134
71
  def store(book)
@@ -138,8 +75,7 @@ module RobustExcelOle
138
75
  # deletes the weak reference to the book
139
76
  @filename2books[old_filename_key].delete(book)
140
77
  end
141
- @filename2books[filename_key] |= [WeakRef.new(book)]
142
- book.stored_filename = book.filename
78
+ @filename2books[filename_key] |= [WeakRef.new(book)]
143
79
  end
144
80
 
145
81
  # creates and returns a separate Excel instance with Visible and DisplayAlerts equal false
@@ -168,7 +104,6 @@ module RobustExcelOle
168
104
 
169
105
  private
170
106
 
171
- # @private
172
107
  def try_hidden_excel
173
108
  @hidden_excel_instance.__getobj__ if @hidden_excel_instance && @hidden_excel_instance.weakref_alive? && @hidden_excel_instance.__getobj__.alive?
174
109
  end
@@ -177,24 +112,26 @@ module RobustExcelOle
177
112
 
178
113
  # prints the book store
179
114
  # @private
180
- def print
181
- # trace "@filename2books:"
115
+ def print_filename2books
116
+ #puts "@filename2books:"
182
117
  if @filename2books
183
- @filename2books.each do |_filename,books|
184
- # trace " filename: #{filename}"
185
- # trace " books:"
118
+ @filename2books.each do |filename,books|
119
+ #puts " filename: #{filename}"
120
+ #puts " books:"
186
121
  if books.empty?
187
- # trace " []"
122
+ #puts " []"
188
123
  else
189
124
  books.each do |book|
190
125
  if book.weakref_alive?
191
- # trace "#{book}"
126
+ #puts "book.filename: #{book.filename}"
192
127
  else # this should never happen
193
- # trace "weakref not alive"
128
+ #puts "weakref not alive"
194
129
  end
195
130
  end
196
131
  end
197
132
  end
133
+ else
134
+ #puts "nil"
198
135
  end
199
136
  end
200
137
  end