roo 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ == 0.8.0 2007-12-15
2
+ * 2 enhancements:
3
+ * Google online spreadsheets were implemented
4
+ * some methods common to more than one class were factored out to the GenericSpreadsheet (virtual) class
1
5
  == 0.7.0 2007-11-23
2
6
  * 6 enhancements:
3
7
  * Openoffice/Excel: the most methods can be called with an option 'sheet'
@@ -6,8 +6,10 @@ Rakefile
6
6
  base64include.rb
7
7
  examples/roo_soap_server.rb
8
8
  examples/roo_soap_client.rb
9
+ examples/write_me.rb
9
10
  lib/roo.rb
10
11
  lib/roo/version.rb
12
+ lib/roo/generic_spreadsheet.rb
11
13
  lib/roo/openoffice.rb
12
14
  lib/roo/excel.rb
13
15
  lib/roo/google.rb
data/README.txt CHANGED
@@ -3,4 +3,3 @@ README for roo
3
3
 
4
4
  see http://roo.rubyforge.org for examples
5
5
 
6
- PLEASE DON'T USE THE Google class - its only experimental.
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ require File.join(File.dirname(__FILE__), 'lib', 'roo', 'version')
14
14
 
15
15
  AUTHOR = 'Thomas Preymesser' # can also be an array of Authors
16
16
  EMAIL = "thopre@gmail.com"
17
- DESCRIPTION = "roo can access the contents of OpenOffice-Spreadsheets and Excel-Spreadsheets"
17
+ DESCRIPTION = "roo can access the contents of OpenOffice-, Excel- or Google-Spreadsheets"
18
18
  GEM_NAME = 'roo' # what ppl will type to install your gem
19
19
 
20
20
  @config_file = "~/.rubyforge/user-config.yml"
@@ -107,7 +107,7 @@ desc 'Generate and upload website files'
107
107
  task :website => [:website_generate, :website_upload]
108
108
 
109
109
  desc 'Release the website and new gem version'
110
- task :deploy => [:check_version, :website, :release] do
110
+ task :deploy => [:check_log_params, :check_version, :website, :release ] do
111
111
  puts "Remember to create SVN tag:"
112
112
  puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
113
113
  "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
@@ -115,6 +115,16 @@ task :deploy => [:check_version, :website, :release] do
115
115
  puts "Tagging release #{CHANGES}"
116
116
  end
117
117
 
118
+ desc 'Check to ensure the LOG_* constant in Test are set off'
119
+ task :check_log_params do
120
+ require 'test/test_roo'
121
+ if DISPLAY_LOG
122
+ raise 'please turn off the DISPLAY_LOG constant for deployment!'
123
+ end
124
+ if DB_LOG
125
+ raise 'please turn off the DB_LOG constant for deployment!'
126
+ end
127
+ end
118
128
  desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
119
129
  task :local_deploy => [:website_generate, :install_gem]
120
130
 
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'roo'
3
+
4
+ #-- create a new spreadsheet within your google-spreadsheets and paste
5
+ #-- the 'key' parameter in the spreadsheet URL
6
+ MAXTRIES = 1000
7
+ print "what's your name? "
8
+ my_name = gets.chomp
9
+ print "where do you live? "
10
+ my_location = gets.chomp
11
+ print "your message? (if left blank, only your name and location will be inserted) "
12
+ my_message = gets.chomp
13
+ spreadsheet = Google.new('ptu6bbahNZpY0N0RrxQbWdw')
14
+ spreadsheet.default_sheet = 'Sheet1'
15
+ success = false
16
+ MAXTRIES.times do
17
+ col = rand(10)+1
18
+ row = rand(10)+1
19
+ if spreadsheet.empty?(row,col)
20
+ if my_message.empty?
21
+ text = Time.now.to_s+" "+"Greetings from #{my_name} (#{my_location})"
22
+ else
23
+ text = Time.now.to_s+" "+"#{my_message} from #{my_name} (#{my_location})"
24
+ end
25
+ spreadsheet.set_value(row,col,text)
26
+ puts "message written to row #{row}, column #{col}"
27
+ success = true
28
+ break
29
+ end
30
+ puts "Row #{row}, column #{col} already occupied, trying again..."
31
+ end
32
+ puts "no empty cell found within #{MAXTRIES} tries" if !success
33
+
data/lib/roo.rb CHANGED
@@ -3,6 +3,7 @@ end
3
3
 
4
4
  require 'roo/version'
5
5
  # require 'roo/spreadsheetparser' TODO:
6
+ require 'roo/generic_spreadsheet'
6
7
  require 'roo/openoffice'
7
8
  require 'roo/excel'
8
9
  require 'roo/google'
@@ -1,24 +1,25 @@
1
1
  require 'rubygems'
2
2
  gem 'parseexcel', '>= 0.5.2'
3
3
  require 'parseexcel'
4
+ CHARGUESS=false
5
+ require 'charguess' if CHARGUESS
4
6
 
5
7
  module Spreadsheet
6
8
  module ParseExcel
7
9
  class Worksheet
8
- include Enumerable
9
- attr_reader :min_row, :max_row, :min_col, :max_col
10
+ include Enumerable
11
+ attr_reader :min_row, :max_row, :min_col, :max_col
10
12
  end
11
13
  end
12
14
  end
13
15
 
14
16
 
15
- # Class Excel is derived from class Openoffice. It implements almost all methods
16
- # from the Openoffice class in the same way.
17
- # Parameter packed: :zip - File is a zip-file
18
- class Excel < Openoffice
17
+ class Excel < GenericSpreadsheet #Openoffice
19
18
 
20
19
  EXCEL_NO_FORMULAS = 'formulas are not supported for excel spreadsheets'
21
20
 
21
+ # Creates a new Excel spreadsheet object.
22
+ # Parameter packed: :zip - File is a zip-file
22
23
  def initialize(filename, packed = nil)
23
24
  @tmpdir = "oo_"+$$.to_s
24
25
  unless File.exists?(@tmpdir)
@@ -26,7 +27,7 @@ class Excel < Openoffice
26
27
  end
27
28
  filename = open_from_uri(filename) if filename[0,7] == "http://"
28
29
  filename = unzip(filename) if packed and packed == :zip
29
- if filename[-4..-1] != ".xls"
30
+ if File.extname(filename) != ".xls"
30
31
  warn "are you sure, this is an excel file?"
31
32
  end
32
33
  @filename = filename
@@ -41,7 +42,7 @@ class Excel < Openoffice
41
42
  end
42
43
  # @first_row = @last_row = @first_column = @last_column = nil
43
44
  #if ENV["roo_local"] != "thomas-p"
44
- FileUtils::rm_r(@tmpdir)
45
+ FileUtils::rm_r(@tmpdir)
45
46
  #end
46
47
  @first_row = Hash.new
47
48
  @last_row = Hash.new
@@ -54,9 +55,19 @@ class Excel < Openoffice
54
55
  result = []
55
56
  0.upto(@workbook.sheet_count - 1) do |i|
56
57
  # TODO: is there a better way to do conversion?
57
- result << Iconv.new('utf-8','unicode').iconv(
58
- @workbook.worksheet(i).name
59
- )
58
+ if CHARGUESS
59
+ encoding = CharGuess::guess(@workbook.worksheet(i).name)
60
+ encoding = 'unicode' unless encoding
61
+
62
+
63
+ result << Iconv.new('utf-8',encoding).iconv(
64
+ @workbook.worksheet(i).name
65
+ )
66
+ else
67
+ result << Iconv.new('utf-8','unicode').iconv(
68
+ @workbook.worksheet(i).name
69
+ )
70
+ end
60
71
  end
61
72
  return result
62
73
  end
@@ -64,14 +75,14 @@ class Excel < Openoffice
64
75
  # sets the working sheet (1,2,3,..)
65
76
  def default_sheet=(n)
66
77
  if n.kind_of?(Fixnum)
67
- @default_sheet = n #-1
78
+ #
68
79
  elsif n.kind_of?(String)
69
80
  raise RangeError if ! self.sheets.include?(n)
70
81
  # parseexcel supports now the name of a sheet
71
- @default_sheet = n
72
82
  else
73
83
  raise TypeError, "what are you trying to set as default sheet?"
74
84
  end
85
+ @default_sheet = n
75
86
  @first_row[n] = @last_row[n] = @first_column[n] = @last_column[n] = nil
76
87
  @cells_read[n] = false
77
88
  end
@@ -91,9 +102,9 @@ class Excel < Openoffice
91
102
  cell = row_par.at(col-1)
92
103
  return nil unless cell
93
104
  case cell.type
94
- when :numeric then return cell.to_f
95
- when :text then return cell.to_s('utf-8')
96
- when :date then return cell.date
105
+ when :numeric then return cell.to_f
106
+ when :text then return cell.to_s('utf-8')
107
+ when :date then return cell.date
97
108
  else
98
109
  return nil # cell.to_s('utf-8')
99
110
  end
@@ -142,9 +153,9 @@ class Excel < Openoffice
142
153
  worksheet.row(rownumber-1).each {|cell|
143
154
  if cell
144
155
  case cell.type
145
- when :numeric then result << cell.to_i
146
- when :text then result << cell.to_s('utf-8')
147
- when :date then result << cell.date
156
+ when :numeric then result << cell.to_i
157
+ when :text then result << cell.to_s('utf-8')
158
+ when :date then result << cell.date
148
159
  else
149
160
  result << cell.to_s('utf-8')
150
161
  end
@@ -169,12 +180,12 @@ class Excel < Openoffice
169
180
  worksheet.each(skip) { |row_par|
170
181
  if defined? row_par.at(columnnumber-1)
171
182
  cell = row_par.at(columnnumber-1)
172
- #if defined? cell = row_par.at(columnnumber-1)
183
+ #if defined? cell = row_par.at(columnnumber-1)
173
184
  if cell
174
185
  case cell.type
175
- when :numeric then result << cell.to_i
176
- when :text then result << cell.to_s('utf-8')
177
- when :date then result << cell.date
186
+ when :numeric then result << cell.to_i
187
+ when :text then result << cell.to_s('utf-8')
188
+ when :date then result << cell.date
178
189
  else
179
190
  result << cell.to_s('utf-8')
180
191
  end
@@ -219,40 +230,23 @@ class Excel < Openoffice
219
230
  fr, lr, fc, lc = get_firsts_lasts(sheet)
220
231
  lr
221
232
  end
222
-
223
- # true if a cell is empty
224
- def empty?(row, col, sheet=nil)
225
- sheet = @default_sheet unless sheet
226
- row,col = normalize(row,col)
227
- return true if row < first_row(sheet) || row > last_row(sheet) || col < first_column(sheet) || col > last_column(sheet)
228
- return true unless cell(row, col, sheet)
229
- return true if celltype(row, col, sheet) == "string" && cell(row, col, sheet) == ""
230
- false
231
- end
232
-
233
- # first non-empty column as a letter
234
- def first_column_as_letter(sheet=nil)
235
- sheet = @default_sheet unless sheet
236
- Openoffice.number_to_letter(first_column(sheet))
237
- end
238
233
 
239
- # last non-empty column as a letter
240
- def last_column_as_letter(sheet=nil)
241
- sheet = @default_sheet unless sheet
242
- Openoffice.number_to_letter(last_column(sheet))
243
- end
244
-
234
+ # returns NO formula in excel spreadsheets
245
235
  def formula(row,col,sheet=nil)
246
236
  raise EXCEL_NO_FORMULAS
247
237
  end
238
+
239
+ # raises an exception because formulas are not supported for excel files
248
240
  def formula?(row,col,sheet=nil)
249
241
  raise EXCEL_NO_FORMULAS
250
242
  end
243
+
244
+ # returns NO formulas in excel spreadsheets
251
245
  def formulas(sheet=nil)
252
246
  raise EXCEL_NO_FORMULAS
253
247
  end
254
248
 
255
- private
249
+ private
256
250
 
257
251
  # check if default_sheet was set
258
252
  def default_sheet_check
@@ -264,42 +258,7 @@ private
264
258
  sheet = @default_sheet unless sheet
265
259
  fr = fc = 999_999
266
260
  lr = lc = -999_999
267
- #TODO: worksheet = @workbook.worksheet(sheet_no(@default_sheet))
268
261
  worksheet = @workbook.worksheet(sheet_no(sheet))
269
- if false #=============================================================
270
- if @filename == "test/false_encoding.xls"
271
- #assert_instance_of(Spreadsheet::ParseExcel::Worksheet, worksheet)
272
- p worksheet.class
273
-
274
- p worksheet.methods
275
- p '### min_row: '+worksheet.min_row.to_s
276
- p '### max_row: '+worksheet.max_row.to_s
277
- p '### min_col: '+worksheet.min_col.to_s
278
- p '### max_col: '+worksheet.max_col.to_s
279
- end
280
- if @filename == "test/false_encoding.xls"
281
- p worksheet
282
- end
283
- skip = 0
284
- line = 1
285
- worksheet.each(skip) { |row_par|
286
- if row_par
287
- if @filename == "test/false_encoding.xls"
288
- p row_par
289
- end
290
- row_par.each_with_index {|cell,i|
291
- # nicht beruecksichtigen, wenn nil und vorher noch nichts war
292
- if cell
293
- fc = [fc, i+1].min
294
- lc = [lc, i+1].max
295
- fr = [fr, line].min
296
- lr = [lr, line].max
297
- end
298
- }
299
- end
300
- line += 1
301
- }
302
- end #=============================================================
303
262
  fr = worksheet.min_row + 1
304
263
  lr = worksheet.max_row + 1
305
264
  fc = worksheet.min_col + 1
@@ -319,19 +278,19 @@ p worksheet.class
319
278
  return fr, lr, fc, lc
320
279
  end
321
280
 
322
-
323
281
  # converts name of a sheet to index (0,1,2,..)
324
282
  def sheet_no(name)
325
283
  return name-1 if name.kind_of?(Fixnum)
326
284
  0.upto(@workbook.sheet_count - 1) do |i|
327
285
  # TODO: is there a better way to do conversion?
328
286
  return i if name == Iconv.new('utf-8','unicode').iconv(
329
- @workbook.worksheet(i).name
330
- )
287
+ @workbook.worksheet(i).name
288
+ )
331
289
  end
332
290
  raise StandardError, "sheet '#{name}' not found"
333
291
  end
334
292
 
293
+ # writes csv output to stdout or file
335
294
  def write_csv_content(file=nil,sheet=nil)
336
295
  file = STDOUT unless file
337
296
  sheet = @default_sheet unless sheet
@@ -345,15 +304,15 @@ p worksheet.class
345
304
  empty = true
346
305
  else
347
306
  case cell.type
348
- when :numeric
349
- onecelltype = :float
350
- onecell = cell.to_f
351
- when :text
352
- onecelltype = :string
353
- onecell = cell.to_s('utf-8')
354
- when :date
355
- onecelltype = :date
356
- onecell = cell.date
307
+ when :numeric
308
+ onecelltype = :float
309
+ onecell = cell.to_f
310
+ when :text
311
+ onecelltype = :string
312
+ onecell = cell.to_s('utf-8')
313
+ when :date
314
+ onecelltype = :date
315
+ onecell = cell.date
357
316
  else
358
317
  onecelltype = nil
359
318
  onecell = nil
@@ -369,7 +328,7 @@ p worksheet.class
369
328
  content = false
370
329
  row.each {|elem|
371
330
  if elem != ''
372
- #if elem.class == String and elem.size > 0
331
+ #if elem.class == String and elem.size > 0
373
332
  content = true
374
333
  end
375
334
  }
@@ -0,0 +1,303 @@
1
+ # Base class for all other types of spreadsheets
2
+ class GenericSpreadsheet
3
+
4
+ attr_reader :default_sheet
5
+
6
+ # converts cell coordinate to numeric values of row,col
7
+ def normalize(row,col)
8
+ if row.class == String
9
+ if col.class == Fixnum
10
+ # ('A',1):
11
+ # ('B', 5) -> (5, 2)
12
+ row, col = col, row
13
+ else
14
+ raise ArgumentError
15
+ end
16
+ end
17
+ if col.class == String
18
+ col = GenericSpreadsheet.letter_to_number(col)
19
+ end
20
+ return row,col
21
+ end
22
+
23
+ # true if cell is empty
24
+ def empty?(row, col, sheet=nil)
25
+ #def excel_empty?(row, col, sheet=nil)
26
+ sheet = @default_sheet unless sheet
27
+ read_cells(sheet) unless @cells_read[sheet] or self.class == Excel
28
+ row,col = normalize(row,col)
29
+ return true unless cell(row, col, sheet)
30
+ return true if celltype(row, col, sheet) == :string && cell(row, col, sheet).empty?
31
+ #false
32
+ #end
33
+
34
+ # true if a cell is empty
35
+ #def oo_empty?(row, col, sheet=nil)
36
+ #sheet = @default_sheet unless sheet
37
+ #read_cells(sheet) unless @cells_read[sheet]
38
+ #row,col = normalize(row,col)
39
+ return true if row < first_row(sheet) || row > last_row(sheet) || col < first_column(sheet) || col > last_column(sheet)
40
+ #return true unless cell(row, col, sheet)
41
+ # return true if celltype(row, col, sheet) == "string" && cell(row, col, sheet) == ""
42
+ #return true if celltype(row, col, sheet) == :string && cell(row, col, sheet) == ""
43
+ false
44
+ end
45
+
46
+
47
+ # reopens and read a spreadsheet document
48
+ def reload
49
+ ds = @default_sheet
50
+ initialize(@filename) if self.class == Openoffice or
51
+ self.class == Excel
52
+ initialize(@spreadsheetkey,@user,@password) if self.class == Google
53
+ self.default_sheet = ds
54
+ #@first_row = @last_row = @first_column = @last_column = nil
55
+ end
56
+
57
+ # Returns information of the spreadsheet document and all sheets within
58
+ # this document.
59
+ def info
60
+ # $log.debug(self.class.to_s+"#info started")
61
+ result = "File: #{@filename}\n"+
62
+ "Number of sheets: #{sheets.size}\n"+
63
+ "Sheets: #{sheets.map{|sheet| sheet+", "}.to_s[0..-3]}\n"
64
+ n = 1
65
+ # $log.debug(sheets.inspect)
66
+ sheets.each {|sheet|
67
+ # $log.debug("Info fuer Sheet=#{sheet}")
68
+ self.default_sheet = sheet
69
+ # $log.debug("nach default_sheet=")
70
+ result << "Sheet " + n.to_s + ":\n"
71
+ result << " First row: #{first_row}\n"
72
+ # $log.debug("nach first_row")
73
+ result << " Last row: #{last_row}\n"
74
+ # $log.debug("nach last_row")
75
+ result << " First column: #{GenericSpreadsheet.number_to_letter(first_column)}\n"
76
+ # $log.debug("nach first_column")
77
+ result << " Last column: #{GenericSpreadsheet.number_to_letter(last_column)}"
78
+ # $log.debug("nach last_column")
79
+ result << "\n" if sheet != sheets.last
80
+ n += 1
81
+ }
82
+ # $log.debug(self.class.to_s+"#info ended")
83
+ result
84
+ end
85
+
86
+ # returns a rectangular area (default: all cells) as yaml-output
87
+ # you can add additional attributes with the prefix parameter like:
88
+ # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
89
+ def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil,sheet=nil)
90
+ sheet = @default_sheet unless sheet
91
+ result = "--- \n"
92
+ (from_row||first_row(sheet)).upto(to_row||last_row(sheet)) do |row|
93
+ (from_column||first_column(sheet)).upto(to_column||last_column(sheet)) do |col|
94
+ unless empty?(row,col,sheet)
95
+ result << "cell_#{row}_#{col}: \n"
96
+ prefix.each {|k,v|
97
+ result << " #{k}: #{v} \n"
98
+ }
99
+ result << " row: #{row} \n"
100
+ result << " col: #{col} \n"
101
+ result << " celltype: #{self.celltype(row,col,sheet)} \n"
102
+ result << " value: #{self.cell(row,col,sheet)} \n"
103
+ end
104
+ end
105
+ end
106
+ result
107
+ end
108
+
109
+ # recursively removes the current temporary directory
110
+ # this is only needed if you work with zipped files or files via the web
111
+ def remove_tmp
112
+ if File.exists?(@tmpdir)
113
+ FileUtils::rm_r(@tmpdir)
114
+ end
115
+ end
116
+
117
+ # first non-empty column as a letter
118
+ def first_column_as_letter(sheet=nil)
119
+ GenericSpreadsheet.number_to_letter(first_column(sheet))
120
+ end
121
+
122
+ # last non-empty column as a letter
123
+ def last_column_as_letter(sheet=nil)
124
+ GenericSpreadsheet.number_to_letter(last_column(sheet))
125
+ end
126
+
127
+ def open_from_uri(uri)
128
+ require 'open-uri' ;
129
+ tempfilename = File.join(@tmpdir, File.basename(uri))
130
+ f = File.open(tempfilename,"wb")
131
+ begin
132
+ open(uri) do |net|
133
+ f.write(net.read)
134
+ end
135
+ rescue
136
+ raise "could not open #{uri}"
137
+ end
138
+ f.close
139
+ File.join(@tmpdir, File.basename(uri))
140
+ end
141
+
142
+ # returns the number of the last non-empty row
143
+ def last_row(sheet=nil)
144
+ sheet = @default_sheet unless sheet
145
+ read_cells(sheet) unless @cells_read[sheet]
146
+ if @last_row[sheet]
147
+ return @last_row[sheet]
148
+ end
149
+ impossible_value = 0
150
+ result = impossible_value
151
+ @cell[sheet].each_pair {|key,value|
152
+ y,x = key.split(',')
153
+ y = y.to_i
154
+ result = [result, y].max if value
155
+ }
156
+ result = nil if result == impossible_value
157
+ @last_row[sheet] = result
158
+ result
159
+ end
160
+
161
+ # returns the number of the last non-empty column
162
+ def last_column(sheet=nil)
163
+ # $log.debug("#{self.class.to_s}#last_column(#{sheet})")
164
+ sheet = @default_sheet unless sheet
165
+ read_cells(sheet) unless @cells_read[sheet]
166
+ if @last_column[sheet]
167
+ # $log.debug("last_column of sheet #{sheet} already set")
168
+ return @last_column[sheet]
169
+ end
170
+ # $log.debug("last_column of sheet #{sheet} not yet set")
171
+ impossible_value = 0
172
+ result = impossible_value
173
+ @cell[sheet].each_pair {|key,value|
174
+ y,x = key.split(',')
175
+ x = x.to_i
176
+ result = [result, x].max if value
177
+ }
178
+ result = nil if result == impossible_value
179
+ @last_column[sheet] = result
180
+ result
181
+ end
182
+
183
+ # returns the number of the first non-empty row
184
+ def first_row(sheet=nil)
185
+ if sheet == nil
186
+ sheet = @default_sheet
187
+ end
188
+ read_cells(sheet) unless @cells_read[sheet]
189
+ if @first_row[sheet]
190
+ return @first_row[sheet]
191
+ end
192
+ impossible_value = 999_999 # more than a spreadsheet can hold
193
+ result = impossible_value
194
+ @cell[sheet].each_pair {|key,value|
195
+ y,x = key.split(',')
196
+ y = y.to_i
197
+ result = [result, y].min if value
198
+ }
199
+ result = nil if result == impossible_value
200
+ @first_row[sheet] = result
201
+ result
202
+ end
203
+
204
+ # returns the number of the first non-empty column
205
+ def first_column(sheet=nil)
206
+ if sheet == nil
207
+ sheet = @default_sheet
208
+ end
209
+ read_cells(sheet) unless @cells_read[sheet]
210
+ if @first_column[sheet]
211
+ return @first_column[sheet]
212
+ end
213
+ impossible_value = 999_999 # more than a spreadsheet can hold
214
+ result = impossible_value
215
+ @cell[sheet].each_pair {|key,value|
216
+ y,x = key.split(',')
217
+ x = x.to_i
218
+ result = [result, x].min if value
219
+ }
220
+ result = nil if result == impossible_value
221
+ @first_column[sheet] = result
222
+ result
223
+ end
224
+
225
+ # convert a number to something like this: 'AB'
226
+ def GenericSpreadsheet.number_to_letter(n)
227
+ letters=""
228
+ while n > 0
229
+ num = n%26
230
+ letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[num-1,1] + letters
231
+ n = n.div(26)
232
+ end
233
+ letters
234
+ end
235
+
236
+ # convert letters like 'AB' to a number
237
+ def GenericSpreadsheet.letter_to_number(letters)
238
+ result = 0
239
+ while letters && letters.length > 0
240
+ character = letters[0,1].upcase
241
+ num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character)
242
+ raise ArgumentError, "invalid column character '#{letters[0,1]}'" if num == nil
243
+ num += 1
244
+ result = result * 26 + num
245
+ letters = letters[1..-1]
246
+ end
247
+ result
248
+ end
249
+
250
+ protected
251
+
252
+ def unzip(filename)
253
+ ret = nil
254
+ Zip::ZipFile.open(filename) do |zip|
255
+ ret = process_zipfile_packed zip
256
+ end
257
+ ret
258
+ end
259
+
260
+ # helper method
261
+ def after(d)
262
+ if DateTime.now > d
263
+ yield
264
+ end
265
+ end
266
+
267
+ # helper method
268
+ def before(d)
269
+ if DateTime.now <= d
270
+ yield
271
+ end
272
+ end
273
+
274
+ private
275
+
276
+ def initialize
277
+
278
+ end
279
+
280
+ def process_zipfile_packed(zip, path='')
281
+ ret=nil
282
+ if zip.file.file? path
283
+ # extract and return filename
284
+ @tmpdir = "oo_"+$$.to_s
285
+ unless File.exists?(@tmpdir)
286
+ FileUtils::mkdir(@tmpdir)
287
+ end
288
+ file = File.open(File.join(@tmpdir, path),"wb")
289
+ file.write(zip.read(path))
290
+ file.close
291
+ return File.join(@tmpdir, path)
292
+ else
293
+ unless path.empty?
294
+ path += '/'
295
+ end
296
+ zip.dir.foreach(path) do |filename|
297
+ ret = process_zipfile_packed(zip, path + filename)
298
+ end
299
+ end
300
+ ret
301
+ end
302
+
303
+ end