roo-immersion 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/History.txt +225 -0
  2. data/README.markdown +60 -0
  3. data/examples/roo_soap_client.rb +53 -0
  4. data/examples/roo_soap_server.rb +29 -0
  5. data/examples/write_me.rb +33 -0
  6. data/lib/roo.rb +32 -0
  7. data/lib/roo/excel.rb +468 -0
  8. data/lib/roo/excel2003xml.rb +394 -0
  9. data/lib/roo/excelx.rb +601 -0
  10. data/lib/roo/generic_spreadsheet.rb +628 -0
  11. data/lib/roo/google.rb +379 -0
  12. data/lib/roo/openoffice.rb +451 -0
  13. data/lib/roo/roo_rails_helper.rb +82 -0
  14. data/lib/roo/version.rb +9 -0
  15. data/test/1900_base.xls +0 -0
  16. data/test/1904_base.xls +0 -0
  17. data/test/Bibelbund.csv +3741 -0
  18. data/test/Bibelbund.ods +0 -0
  19. data/test/Bibelbund.xls +0 -0
  20. data/test/Bibelbund.xlsx +0 -0
  21. data/test/Bibelbund.xml +62518 -0
  22. data/test/Bibelbund1.ods +0 -0
  23. data/test/bad_excel_date.xls +0 -0
  24. data/test/bbu.ods +0 -0
  25. data/test/bbu.xls +0 -0
  26. data/test/bbu.xlsx +0 -0
  27. data/test/bbu.xml +152 -0
  28. data/test/bode-v1.ods.zip +0 -0
  29. data/test/bode-v1.xls.zip +0 -0
  30. data/test/boolean.ods +0 -0
  31. data/test/boolean.xls +0 -0
  32. data/test/boolean.xlsx +0 -0
  33. data/test/boolean.xml +112 -0
  34. data/test/borders.ods +0 -0
  35. data/test/borders.xls +0 -0
  36. data/test/borders.xlsx +0 -0
  37. data/test/borders.xml +144 -0
  38. data/test/bug-row-column-fixnum-float.xls +0 -0
  39. data/test/bug-row-column-fixnum-float.xml +127 -0
  40. data/test/datetime.ods +0 -0
  41. data/test/datetime.xls +0 -0
  42. data/test/datetime.xlsx +0 -0
  43. data/test/datetime.xml +142 -0
  44. data/test/datetime_floatconv.xls +0 -0
  45. data/test/datetime_floatconv.xml +148 -0
  46. data/test/emptysheets.ods +0 -0
  47. data/test/emptysheets.xls +0 -0
  48. data/test/emptysheets.xml +105 -0
  49. data/test/excel2003.xml +21140 -0
  50. data/test/false_encoding.xls +0 -0
  51. data/test/false_encoding.xml +132 -0
  52. data/test/formula.ods +0 -0
  53. data/test/formula.xls +0 -0
  54. data/test/formula.xlsx +0 -0
  55. data/test/formula.xml +134 -0
  56. data/test/formula_parse_error.xls +0 -0
  57. data/test/formula_parse_error.xml +1833 -0
  58. data/test/html-escape.ods +0 -0
  59. data/test/no_spreadsheet_file.txt +1 -0
  60. data/test/numbers1.csv +18 -0
  61. data/test/numbers1.ods +0 -0
  62. data/test/numbers1.xls +0 -0
  63. data/test/numbers1.xlsx +0 -0
  64. data/test/numbers1.xml +312 -0
  65. data/test/only_one_sheet.ods +0 -0
  66. data/test/only_one_sheet.xls +0 -0
  67. data/test/only_one_sheet.xlsx +0 -0
  68. data/test/only_one_sheet.xml +67 -0
  69. data/test/paragraph.ods +0 -0
  70. data/test/paragraph.xls +0 -0
  71. data/test/paragraph.xlsx +0 -0
  72. data/test/paragraph.xml +127 -0
  73. data/test/ric.ods +0 -0
  74. data/test/simple_spreadsheet.ods +0 -0
  75. data/test/simple_spreadsheet.xls +0 -0
  76. data/test/simple_spreadsheet.xlsx +0 -0
  77. data/test/simple_spreadsheet.xml +225 -0
  78. data/test/simple_spreadsheet_from_italo.ods +0 -0
  79. data/test/simple_spreadsheet_from_italo.xls +0 -0
  80. data/test/simple_spreadsheet_from_italo.xml +242 -0
  81. data/test/skipped_tests.rb +789 -0
  82. data/test/style.ods +0 -0
  83. data/test/style.xls +0 -0
  84. data/test/style.xlsx +0 -0
  85. data/test/style.xml +154 -0
  86. data/test/test_helper.rb +19 -0
  87. data/test/test_roo.rb +1834 -0
  88. data/test/time-test.csv +2 -0
  89. data/test/time-test.ods +0 -0
  90. data/test/time-test.xls +0 -0
  91. data/test/time-test.xlsx +0 -0
  92. data/test/time-test.xml +131 -0
  93. data/test/whitespace.ods +0 -0
  94. data/test/whitespace.xls +0 -0
  95. data/test/whitespace.xlsx +0 -0
  96. data/test/whitespace.xml +184 -0
  97. metadata +231 -0
@@ -0,0 +1,628 @@
1
+ require 'rubygems'
2
+ require 'builder'
3
+
4
+ # Base class for all other types of spreadsheets
5
+ class GenericSpreadsheet
6
+
7
+ attr_reader :default_sheet
8
+
9
+ # sets the line with attribute names (default: 1)
10
+ attr_accessor :header_line
11
+
12
+ def initialize
13
+ end
14
+
15
+ # set the working sheet in the document
16
+ def default_sheet=(sheet)
17
+ if sheet.kind_of? Fixnum
18
+ if sheet >= 0 and sheet <= sheets.length
19
+ sheet = self.sheets[sheet-1]
20
+ else
21
+ raise RangeError
22
+ end
23
+ elsif sheet.kind_of?(String)
24
+ raise RangeError if ! self.sheets.include?(sheet)
25
+ else
26
+ raise TypeError, "what are you trying to set as default sheet?"
27
+ end
28
+ @default_sheet = sheet
29
+ check_default_sheet
30
+ @first_row[sheet] = @last_row[sheet] = @first_column[sheet] = @last_column[sheet] = nil
31
+ @cells_read[sheet] = false
32
+ end
33
+
34
+ # first non-empty column as a letter
35
+ def first_column_as_letter(sheet=nil)
36
+ GenericSpreadsheet.number_to_letter(first_column(sheet))
37
+ end
38
+
39
+ # last non-empty column as a letter
40
+ def last_column_as_letter(sheet=nil)
41
+ GenericSpreadsheet.number_to_letter(last_column(sheet))
42
+ end
43
+
44
+ # returns the number of the first non-empty row
45
+ def first_row(sheet=nil)
46
+ if sheet == nil
47
+ sheet = @default_sheet
48
+ end
49
+ read_cells(sheet) unless @cells_read[sheet]
50
+ if @first_row[sheet]
51
+ return @first_row[sheet]
52
+ end
53
+ impossible_value = 999_999 # more than a spreadsheet can hold
54
+ result = impossible_value
55
+ @cell[sheet].each_pair {|key,value|
56
+ y,x = key # _to_string(key).split(',')
57
+ y = y.to_i
58
+ result = [result, y].min if value
59
+ } if @cell[sheet]
60
+ result = nil if result == impossible_value
61
+ @first_row[sheet] = result
62
+ result
63
+ end
64
+
65
+ # returns the number of the last non-empty row
66
+ def last_row(sheet=nil)
67
+ sheet = @default_sheet unless sheet
68
+ read_cells(sheet) unless @cells_read[sheet]
69
+ if @last_row[sheet]
70
+ return @last_row[sheet]
71
+ end
72
+ impossible_value = 0
73
+ result = impossible_value
74
+ @cell[sheet].each_pair {|key,value|
75
+ y,x = key # _to_string(key).split(',')
76
+ y = y.to_i
77
+ result = [result, y].max if value
78
+ } if @cell[sheet]
79
+ result = nil if result == impossible_value
80
+ @last_row[sheet] = result
81
+ result
82
+ end
83
+
84
+ # returns the number of the first non-empty column
85
+ def first_column(sheet=nil)
86
+ if sheet == nil
87
+ sheet = @default_sheet
88
+ end
89
+ read_cells(sheet) unless @cells_read[sheet]
90
+ if @first_column[sheet]
91
+ return @first_column[sheet]
92
+ end
93
+ impossible_value = 999_999 # more than a spreadsheet can hold
94
+ result = impossible_value
95
+ @cell[sheet].each_pair {|key,value|
96
+ y,x = key # _to_string(key).split(',')
97
+ x = x # .to_i
98
+ result = [result, x].min if value
99
+ } if @cell[sheet]
100
+ result = nil if result == impossible_value
101
+ @first_column[sheet] = result
102
+ result
103
+ end
104
+
105
+ # returns the number of the last non-empty column
106
+ def last_column(sheet=nil)
107
+ sheet = @default_sheet unless sheet
108
+ read_cells(sheet) unless @cells_read[sheet]
109
+ if @last_column[sheet]
110
+ return @last_column[sheet]
111
+ end
112
+ impossible_value = 0
113
+ result = impossible_value
114
+ @cell[sheet].each_pair {|key,value|
115
+ y,x = key # _to_string(key).split(',')
116
+ x = x.to_i
117
+ result = [result, x].max if value
118
+ } if @cell[sheet]
119
+ result = nil if result == impossible_value
120
+ @last_column[sheet] = result
121
+ result
122
+ end
123
+
124
+ # returns a rectangular area (default: all cells) as yaml-output
125
+ # you can add additional attributes with the prefix parameter like:
126
+ # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
127
+ def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil,sheet=nil)
128
+ sheet = @default_sheet unless sheet
129
+ result = "--- \n"
130
+ (from_row||first_row(sheet)).upto(to_row||last_row(sheet)) do |row|
131
+ (from_column||first_column(sheet)).upto(to_column||last_column(sheet)) do |col|
132
+ unless empty?(row,col,sheet)
133
+ result << "cell_#{row}_#{col}: \n"
134
+ prefix.each {|k,v|
135
+ result << " #{k}: #{v} \n"
136
+ }
137
+ result << " row: #{row} \n"
138
+ result << " col: #{col} \n"
139
+ result << " celltype: #{self.celltype(row,col,sheet)} \n"
140
+ if self.celltype(row,col,sheet) == :time
141
+ result << " value: #{GenericSpreadsheet.integer_to_timestring( self.cell(row,col,sheet))} \n"
142
+ else
143
+ result << " value: #{self.cell(row,col,sheet)} \n"
144
+ end
145
+ end
146
+ end
147
+ end
148
+ result
149
+ end
150
+
151
+ # write the current spreadsheet to stdout or into a file
152
+ def to_csv(filename=nil,sheet=nil)
153
+ sheet = @default_sheet unless sheet
154
+ if filename
155
+ file = File.open(filename,"w") # do |file|
156
+ write_csv_content(file,sheet)
157
+ file.close
158
+ else
159
+ write_csv_content(STDOUT,sheet)
160
+ end
161
+ true
162
+ end
163
+
164
+ # find a row either by row number or a condition
165
+ # Caution: this works only within the default sheet -> set default_sheet before you call this method
166
+ # (experimental. see examples in the test_roo.rb file)
167
+ def find(*args) # :nodoc
168
+ result_array = false
169
+ args.each {|arg,val|
170
+ if arg.class == Hash
171
+ arg.each { |hkey,hval|
172
+ if hkey == :array and hval == true
173
+ result_array = true
174
+ end
175
+ }
176
+ end
177
+ }
178
+ column_with = {}
179
+ 1.upto(last_column) do |col|
180
+ column_with[cell(@header_line,col)] = col
181
+ end
182
+ result = Array.new
183
+ #-- id
184
+ if args[0].class == Fixnum
185
+ rownum = args[0]
186
+ if @header_line
187
+ tmp = {}
188
+ else
189
+ tmp = []
190
+ end
191
+ 1.upto(self.row(rownum).size) {|j|
192
+ x = ''
193
+ column_with.each { |key,val|
194
+ if val == j
195
+ x = key
196
+ end
197
+ }
198
+ if @header_line
199
+ tmp[x] = cell(rownum,j)
200
+ else
201
+ tmp[j-1] = cell(rownum,j)
202
+ end
203
+
204
+ }
205
+ if @header_line
206
+ result = [ tmp ]
207
+ else
208
+ result = tmp
209
+ end
210
+ #-- :all
211
+ elsif args[0] == :all
212
+ if args[1].class == Hash
213
+ args[1].each {|key,val|
214
+ if key == :conditions
215
+ column_with = {}
216
+ 1.upto(last_column) do |col|
217
+ column_with[cell(@header_line,col)] = col
218
+ end
219
+ conditions = val
220
+ first_row.upto(last_row) do |i|
221
+ # are all conditions met?
222
+ found = 1
223
+ conditions.each { |key,val|
224
+ if cell(i,column_with[key]) == val
225
+ found *= 1
226
+ else
227
+ found *= 0
228
+ end
229
+ }
230
+ if found > 0
231
+ tmp = {}
232
+ 1.upto(self.row(i).size) {|j|
233
+ x = ''
234
+ column_with.each { |key,val|
235
+ if val == j
236
+ x = key
237
+ end
238
+ }
239
+ tmp[x] = cell(i,j)
240
+ }
241
+ if result_array
242
+ result << self.row(i)
243
+ else
244
+ result << tmp
245
+ end
246
+ end
247
+ end
248
+ end # :conditions
249
+ }
250
+ end
251
+ end
252
+ result
253
+ end
254
+
255
+ # returns all values in this row as an array
256
+ # row numbers are 1,2,3,... like in the spreadsheet
257
+ def row(rownumber,sheet=nil)
258
+ sheet = @default_sheet unless sheet
259
+ read_cells(sheet) unless @cells_read[sheet]
260
+ result = []
261
+ first_column(sheet).upto(last_column(sheet)) do |col|
262
+ result << cell(rownumber,col,sheet)
263
+ end
264
+ result
265
+ end
266
+
267
+ # returns all values in this column as an array
268
+ # column numbers are 1,2,3,... like in the spreadsheet
269
+ def column(columnnumber,sheet=nil)
270
+ if columnnumber.class == String
271
+ columnnumber = Excel.letter_to_number(columnnumber)
272
+ end
273
+ sheet = @default_sheet unless sheet
274
+ read_cells(sheet) unless @cells_read[sheet]
275
+ result = []
276
+ first_row(sheet).upto(last_row(sheet)) do |row|
277
+ result << cell(row,columnnumber,sheet)
278
+ end
279
+ result
280
+ end
281
+
282
+ # reopens and read a spreadsheet document
283
+ def reload
284
+ ds = @default_sheet
285
+ initialize(@filename) if self.class == Openoffice or
286
+ self.class == Excel
287
+ initialize(@spreadsheetkey,@user,@password) if self.class == Google
288
+ self.default_sheet = ds
289
+ #@first_row = @last_row = @first_column = @last_column = nil
290
+ end
291
+
292
+ # true if cell is empty
293
+ def empty?(row, col, sheet=nil)
294
+ sheet = @default_sheet unless sheet
295
+ read_cells(sheet) unless @cells_read[sheet] or self.class == Excel
296
+ row,col = normalize(row,col)
297
+ return true unless cell(row, col, sheet)
298
+ return true if celltype(row, col, sheet) == :string && cell(row, col, sheet).empty?
299
+ return true if row < first_row(sheet) || row > last_row(sheet) || col < first_column(sheet) || col > last_column(sheet)
300
+ false
301
+ end
302
+
303
+ # recursively removes the current temporary directory
304
+ # this is only needed if you work with zipped files or files via the web
305
+ def remove_tmp
306
+ if File.exists?(@tmpdir)
307
+ FileUtils::rm_r(@tmpdir)
308
+ end
309
+ end
310
+
311
+ # Returns information of the spreadsheet document and all sheets within
312
+ # this document.
313
+ def info
314
+ result = "File: #{File.basename(@filename)}\n"+
315
+ "Number of sheets: #{sheets.size}\n"+
316
+ "Sheets: #{sheets.map{|sheet| sheet+", "}.to_s[0..-3]}\n"
317
+ n = 1
318
+ sheets.each {|sheet|
319
+ self.default_sheet = sheet
320
+ result << "Sheet " + n.to_s + ":\n"
321
+ unless first_row
322
+ result << " - empty -"
323
+ else
324
+ result << " First row: #{first_row}\n"
325
+ result << " Last row: #{last_row}\n"
326
+ result << " First column: #{GenericSpreadsheet.number_to_letter(first_column)}\n"
327
+ result << " Last column: #{GenericSpreadsheet.number_to_letter(last_column)}"
328
+ end
329
+ result << "\n" if sheet != sheets.last
330
+ n += 1
331
+ }
332
+ result
333
+ end
334
+
335
+ def to_xml
336
+ xml_document = ''
337
+ xml = Builder::XmlMarkup.new(:target => xml_document, :indent => 2)
338
+ xml.instruct! :xml, :version =>"1.0", :encoding => "utf-8"
339
+ xml.spreadsheet {
340
+ self.sheets.each do |sheet|
341
+ self.default_sheet = sheet
342
+ xml.sheet(:name => sheet) { |x|
343
+ if first_row and last_row and first_column and last_column
344
+ # sonst gibt es Fehler bei leeren Blaettern
345
+ first_row.upto(last_row) do |row|
346
+ first_column.upto(last_column) do |col|
347
+ unless empty?(row,col)
348
+ x.cell(cell(row,col),
349
+ :row =>row,
350
+ :column => col,
351
+ :type => celltype(row,col))
352
+ end
353
+ end
354
+ end
355
+ end
356
+ }
357
+ end
358
+ }
359
+ xml_document
360
+ end
361
+
362
+ protected
363
+
364
+ def file_type_check(filename, ext, name)
365
+ new_expression = {
366
+ '.ods' => 'Openoffice.new',
367
+ '.xls' => 'Excel.new',
368
+ '.xlsx' => 'Excelx.new',
369
+ '.xml' => 'Excel2003.new'
370
+ }
371
+ case ext
372
+ when '.ods', '.xls', '.xlsx', '.xml'
373
+ correct_class = "use #{new_expression[ext]} to handle #{ext} spreadsheet files"
374
+ else
375
+ raise "unknown file type: #{ext}"
376
+ end
377
+ if File.extname(filename).downcase != ext
378
+ case @file_warning
379
+ when :error
380
+ warn correct_class
381
+ raise TypeError, "#{filename} is not #{name} file"
382
+ when :warning
383
+ warn "are you sure, this is #{name} spreadsheet file?"
384
+ warn correct_class
385
+ when :ignore
386
+ # ignore
387
+ else
388
+ raise "#{@file_warning} illegal state of file_warning"
389
+ end
390
+ end
391
+ end
392
+
393
+ # konvertiert einen Key in der Form "12,45" (=row,column) in
394
+ # ein Array mit numerischen Werten ([12,45])
395
+ # Diese Methode ist eine temp. Loesung, um zu erforschen, ob der
396
+ # Zugriff mit numerischen Keys schneller ist.
397
+ def key_to_num(str)
398
+ r,c = str.split(',')
399
+ r = r.to_i
400
+ c = c.to_i
401
+ [r,c]
402
+ end
403
+
404
+ # siehe: key_to_num
405
+ def key_to_string(arr)
406
+ "#{arr[0]},#{arr[1]}"
407
+ end
408
+
409
+ private
410
+
411
+ # converts cell coordinate to numeric values of row,col
412
+ def normalize(row,col)
413
+ if row.class == String
414
+ if col.class == Fixnum
415
+ # ('A',1):
416
+ # ('B', 5) -> (5, 2)
417
+ row, col = col, row
418
+ else
419
+ raise ArgumentError
420
+ end
421
+ end
422
+ if col.class == String
423
+ col = GenericSpreadsheet.letter_to_number(col)
424
+ end
425
+ return row,col
426
+ end
427
+
428
+ # def open_from_uri(uri)
429
+ # require 'open-uri' ;
430
+ # tempfilename = File.join(@tmpdir, File.basename(uri))
431
+ # f = File.open(tempfilename,"wb")
432
+ # begin
433
+ # open(uri) do |net|
434
+ # f.write(net.read)
435
+ # end
436
+ # rescue
437
+ # raise "could not open #{uri}"
438
+ # end
439
+ # f.close
440
+ # File.join(@tmpdir, File.basename(uri))
441
+ # end
442
+
443
+ # OpenURI::HTTPError
444
+ # def open_from_uri(uri)
445
+ # require 'open-uri'
446
+ # #existiert URL?
447
+ # r = Net::HTTP.get_response(URI.parse(uri))
448
+ # raise "URL nicht verfuegbar" unless r.is_a? Net::HTTPOK
449
+ # tempfilename = File.join(@tmpdir, File.basename(uri))
450
+ # f = File.open(tempfilename,"wb")
451
+ # open(uri) do |net|
452
+ # f.write(net.read)
453
+ # end
454
+ # # rescue
455
+ # # raise "could not open #{uri}"
456
+ # # end
457
+ # f.close
458
+ # File.join(@tmpdir, File.basename(uri))
459
+ # end
460
+
461
+ def open_from_uri(uri)
462
+ require 'open-uri'
463
+ response = ''
464
+ begin
465
+ open(uri, "User-Agent" => "Ruby/#{RUBY_VERSION}") { |net|
466
+ response = net.read
467
+ tempfilename = File.join(@tmpdir, File.basename(uri))
468
+ f = File.open(tempfilename,"wb")
469
+ f.write(response)
470
+ f.close
471
+ }
472
+ rescue OpenURI::HTTPError
473
+ raise "could not open #{uri}"
474
+ end
475
+ File.join(@tmpdir, File.basename(uri))
476
+ end
477
+
478
+ def open_from_stream(stream)
479
+ tempfilename = File.join(@tmpdir, "spreadsheet")
480
+ f = File.open(tempfilename,"wb")
481
+ f.write(stream[7..-1])
482
+ f.close
483
+ File.join(@tmpdir, "spreadsheet")
484
+ end
485
+
486
+ # convert a number to something like 'AB' (1 => 'A', 2 => 'B', ...)
487
+ def self.number_to_letter(n)
488
+ letters=""
489
+ while n > 0
490
+ num = n%26
491
+ letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[num-1,1] + letters
492
+ n = n.div(26)
493
+ end
494
+ letters
495
+ end
496
+
497
+ # convert letters like 'AB' to a number ('A' => 1, 'B' => 2, ...)
498
+ def self.letter_to_number(letters)
499
+ result = 0
500
+ while letters && letters.length > 0
501
+ character = letters[0,1].upcase
502
+ num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character)
503
+ raise ArgumentError, "invalid column character '#{letters[0,1]}'" if num == nil
504
+ num += 1
505
+ result = result * 26 + num
506
+ letters = letters[1..-1]
507
+ end
508
+ result
509
+ end
510
+
511
+ def unzip(filename)
512
+ ret = nil
513
+ Zip::ZipFile.open(filename) do |zip|
514
+ ret = process_zipfile_packed zip
515
+ end
516
+ ret
517
+ end
518
+
519
+ # check if default_sheet was set and exists in sheets-array
520
+ def check_default_sheet
521
+ sheet_found = false
522
+ raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
523
+ if sheets.index(@default_sheet)
524
+ sheet_found = true
525
+ end
526
+ if ! sheet_found
527
+ raise RangeError, "sheet '#{@default_sheet}' not found"
528
+ end
529
+ #raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
530
+ end
531
+
532
+ def process_zipfile_packed(zip, path='')
533
+ ret=nil
534
+ if zip.file.file? path
535
+ # extract and return filename
536
+ @tmpdir = "oo_"+$$.to_s
537
+ unless File.exists?(@tmpdir)
538
+ FileUtils::mkdir(@tmpdir)
539
+ end
540
+ file = File.open(File.join(@tmpdir, path),"wb")
541
+ file.write(zip.read(path))
542
+ file.close
543
+ return File.join(@tmpdir, path)
544
+ else
545
+ unless path.empty?
546
+ path += '/'
547
+ end
548
+ zip.dir.foreach(path) do |filename|
549
+ ret = process_zipfile_packed(zip, path + filename)
550
+ end
551
+ end
552
+ ret
553
+ end
554
+
555
+ def write_csv_content(file=nil,sheet=nil)
556
+ file = STDOUT unless file
557
+ if first_row(sheet) # sheet is not empty
558
+ # first_row(sheet).upto(last_row(sheet)) do |row|
559
+ 1.upto(last_row(sheet)) do |row|
560
+ 1.upto(last_column(sheet)) do |col|
561
+ file.print(",") if col > 1
562
+ onecell = cell(row,col,sheet)
563
+ onecelltype = celltype(row,col,sheet)
564
+ file.print one_cell_output(onecelltype,onecell,empty?(row,col,sheet))
565
+ end
566
+ file.print("\n")
567
+ end # sheet not empty
568
+ end
569
+ end
570
+
571
+ def one_cell_output(onecelltype,onecell,empty)
572
+ str = ""
573
+ if empty
574
+ str += ''
575
+ else
576
+ case onecelltype
577
+ when :string
578
+ if onecell == ""
579
+ str << ''
580
+ else
581
+ onecell.gsub!(/"/,'""')
582
+ str << ('"'+onecell+'"')
583
+ end
584
+ when :float,:percentage
585
+ if onecell == onecell.to_i
586
+ str << onecell.to_i.to_s
587
+ else
588
+ str << onecell.to_s
589
+ end
590
+ when :formula
591
+ if onecell.class == String
592
+ if onecell == ""
593
+ str << ''
594
+ else
595
+ onecell.gsub!(/"/,'""')
596
+ str << '"'+onecell+'"'
597
+ end
598
+ elsif onecell.class == Float
599
+ if onecell == onecell.to_i
600
+ str << onecell.to_i.to_s
601
+ else
602
+ str << onecell.to_s
603
+ end
604
+ else
605
+ raise "unhandled onecell-class "+onecell.class.to_s
606
+ end
607
+ when :date
608
+ str << onecell.to_s
609
+ when :time
610
+ str << GenericSpreadsheet.integer_to_timestring(onecell)
611
+ else
612
+ raise "unhandled celltype "+onecelltype.to_s
613
+ end
614
+ end
615
+ str
616
+ end
617
+
618
+ # converts an integer value to a time string like '02:05:06'
619
+ def self.integer_to_timestring(content)
620
+ return content if String === content
621
+ h = (content/3600.0).floor
622
+ content = content - h*3600
623
+ m = (content/60.0).floor
624
+ content = content - m*60
625
+ s = content
626
+ sprintf("%02d:%02d:%02d",h,m,s)
627
+ end
628
+ end