hmcgowan-roo 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/History.txt +225 -0
  2. data/README.txt +43 -0
  3. data/lib/roo/excel.rb +455 -0
  4. data/lib/roo/excelx.rb +654 -0
  5. data/lib/roo/generic_spreadsheet.rb +636 -0
  6. data/lib/roo/google.rb +411 -0
  7. data/lib/roo/openoffice.rb +508 -0
  8. data/lib/roo/roo_rails_helper.rb +81 -0
  9. data/lib/roo/version.rb +9 -0
  10. data/lib/roo.rb +11 -0
  11. data/test/Bibelbund.csv +3741 -0
  12. data/test/Bibelbund.ods +0 -0
  13. data/test/Bibelbund.xls +0 -0
  14. data/test/Bibelbund.xlsx +0 -0
  15. data/test/Bibelbund1.ods +0 -0
  16. data/test/bbu.ods +0 -0
  17. data/test/bbu.xls +0 -0
  18. data/test/bbu.xlsx +0 -0
  19. data/test/bode-v1.ods.zip +0 -0
  20. data/test/bode-v1.xls.zip +0 -0
  21. data/test/boolean.ods +0 -0
  22. data/test/boolean.xls +0 -0
  23. data/test/boolean.xlsx +0 -0
  24. data/test/borders.ods +0 -0
  25. data/test/borders.xls +0 -0
  26. data/test/borders.xlsx +0 -0
  27. data/test/bug-row-column-fixnum-float.xls +0 -0
  28. data/test/datetime.ods +0 -0
  29. data/test/datetime.xls +0 -0
  30. data/test/datetime.xlsx +0 -0
  31. data/test/emptysheets.ods +0 -0
  32. data/test/emptysheets.xls +0 -0
  33. data/test/false_encoding.xls +0 -0
  34. data/test/formula.ods +0 -0
  35. data/test/formula.xls +0 -0
  36. data/test/formula.xlsx +0 -0
  37. data/test/html-escape.ods +0 -0
  38. data/test/no_spreadsheet_file.txt +1 -0
  39. data/test/numbers1.csv +18 -0
  40. data/test/numbers1.ods +0 -0
  41. data/test/numbers1.xls +0 -0
  42. data/test/numbers1.xlsx +0 -0
  43. data/test/numbers1_excel.csv +18 -0
  44. data/test/only_one_sheet.ods +0 -0
  45. data/test/only_one_sheet.xls +0 -0
  46. data/test/only_one_sheet.xlsx +0 -0
  47. data/test/ric.ods +0 -0
  48. data/test/simple_spreadsheet.ods +0 -0
  49. data/test/simple_spreadsheet.xls +0 -0
  50. data/test/simple_spreadsheet.xlsx +0 -0
  51. data/test/simple_spreadsheet_from_italo.ods +0 -0
  52. data/test/simple_spreadsheet_from_italo.xls +0 -0
  53. data/test/style.ods +0 -0
  54. data/test/style.xls +0 -0
  55. data/test/style.xlsx +0 -0
  56. data/test/test_helper.rb +19 -0
  57. data/test/test_roo.rb +4946 -0
  58. data/test/time-test.csv +2 -0
  59. data/test/time-test.ods +0 -0
  60. data/test/time-test.xls +0 -0
  61. data/test/time-test.xlsx +0 -0
  62. metadata +225 -0
data/lib/roo/google.rb ADDED
@@ -0,0 +1,411 @@
1
+ require 'gdata/spreadsheet'
2
+
3
+ # overwrite some methods from the gdata-gem:
4
+ module GData
5
+ class Spreadsheet < GData::Base
6
+ #-- modified
7
+ def evaluate_cell(cell, sheet_no=1)
8
+ raise ArgumentError, "invalid cell: #{cell}" unless cell
9
+ raise ArgumentError, "invalid sheet_no: #{sheet_no}" unless sheet_no >0 and sheet_no.class == Fixnum
10
+ path = "/feeds/cells/#{@spreadsheet_id}/#{sheet_no}/#{@headers ? "private" : "public"}/basic/#{cell}"
11
+
12
+ doc = Hpricot(request(path))
13
+ result = (doc/"content").inner_html
14
+ end
15
+
16
+ #-- new
17
+ def sheetlist
18
+ path = "/feeds/worksheets/#{@spreadsheet_id}/private/basic"
19
+ doc = Hpricot(request(path))
20
+ result = []
21
+ (doc/"content").each { |elem|
22
+ result << elem.inner_html
23
+ }
24
+ result
25
+ end
26
+
27
+ #-- new
28
+ #@@ added sheet_no to definition
29
+ def save_entry_roo(entry, sheet_no)
30
+ path = "/feeds/cells/#{@spreadsheet_id}/#{sheet_no}/#{@headers ? 'private' : 'public'}/full"
31
+ post(path, entry)
32
+ end
33
+
34
+ #-- new
35
+ def entry_roo(formula, row=1, col=1)
36
+ <<XML
37
+ <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gs='http://schemas.google.com/spreadsheets/2006'>
38
+ <gs:cell row='#{row}' col='#{col}' inputValue='#{formula}' />
39
+ </entry>
40
+ XML
41
+ end
42
+
43
+ #-- new
44
+ #@@ added sheet_no to definition
45
+ def add_to_cell_roo(row,col,value, sheet_no=1)
46
+ save_entry_roo(entry_roo(value,row,col), sheet_no)
47
+ end
48
+
49
+ #-- new
50
+ def get_one_sheet
51
+ path = "/feeds/cells/#{@spreadsheet_id}/1/private/full"
52
+ doc = Hpricot(request(path))
53
+ end
54
+
55
+ #new
56
+ def oben_unten_links_rechts(sheet_no)
57
+ path = "/feeds/cells/#{@spreadsheet_id}/#{sheet_no}/private/full"
58
+ doc = Hpricot(request(path))
59
+ rows = []
60
+ cols = []
61
+ (doc/"gs:cell").each {|item|
62
+ rows.push item['row'].to_i
63
+ cols.push item['col'].to_i
64
+ }
65
+ return rows.min, rows.max, cols.min, cols.max
66
+ end
67
+
68
+ def fulldoc(sheet_no)
69
+ path = "/feeds/cells/#{@spreadsheet_id}/#{sheet_no}/private/full"
70
+ doc = Hpricot(request(path))
71
+ return doc
72
+ end
73
+ end # class
74
+ end # module
75
+
76
+ class Google < GenericSpreadsheet
77
+
78
+ # Creates a new Google spreadsheet object.
79
+ def initialize(spreadsheetkey,user=nil,password=nil)
80
+ @filename = spreadsheetkey
81
+ @spreadsheetkey = spreadsheetkey
82
+ @user = user
83
+ @password = password
84
+ unless user
85
+ user = ENV['GOOGLE_MAIL']
86
+ end
87
+ unless password
88
+ password = ENV['GOOGLE_PASSWORD']
89
+ end
90
+ @default_sheet = nil
91
+ @cell = Hash.new
92
+ @cell_type = Hash.new
93
+ @formula = Hash.new
94
+ @first_row = Hash.new
95
+ @last_row = Hash.new
96
+ @first_column = Hash.new
97
+ @last_column = Hash.new
98
+ @cells_read = Hash.new
99
+ @header_line = 1
100
+
101
+ @gs = GData::Spreadsheet.new(spreadsheetkey)
102
+ @gs.authenticate(user, password)
103
+
104
+ #-- ----------------------------------------------------------------------
105
+ #-- TODO: Behandlung von Berechtigungen hier noch einbauen ???
106
+ #-- ----------------------------------------------------------------------
107
+
108
+ if self.sheets.size == 1
109
+ @default_sheet = self.sheets.first
110
+ end
111
+ end
112
+
113
+ # returns an array of sheet names in the spreadsheet
114
+ def sheets
115
+ return @gs.sheetlist
116
+ end
117
+
118
+ # is String a date with format DD/MM/YYYY
119
+ def Google.date?(string)
120
+ return false if string.class == Float
121
+ return true if string.class == Date
122
+ return string.strip =~ /^([0-9]+)\/([0-9]+)\/([0-9]+)$/
123
+ end
124
+
125
+ # is String a time with format HH:MM:SS?
126
+ def Google.time?(string)
127
+ return false if string.class == Float
128
+ return true if string.class == Date
129
+ return string.strip =~ /^([0-9]+):([0-9]+):([0-9]+)$/
130
+ end
131
+
132
+ # is String a date+time with format DD/MM/YYYY HH:MM:SS
133
+ def Google.datetime?(string)
134
+ return false if string.class == Float
135
+ return true if string.class == Date
136
+ return string.strip =~ /^([0-9]+)\/([0-9]+)\/([0-9]+)\ ([0-9]+):([0-9]+):([0-9]+)$/
137
+ end
138
+
139
+
140
+ def Google.timestring_to_seconds(value)
141
+ hms = value.split(':')
142
+ hms[0].to_i*3600 + hms[1].to_i*60 + hms[2].to_i
143
+ end
144
+
145
+ # Returns the content of a spreadsheet-cell.
146
+ # (1,1) is the upper left corner.
147
+ # (1,1), (1,'A'), ('A',1), ('a',1) all refers to the
148
+ # cell at the first line and first row.
149
+ def cell(row, col, sheet=nil)
150
+ sheet = @default_sheet unless sheet
151
+ check_default_sheet #TODO: 2007-12-16
152
+ read_cells(sheet) unless @cells_read[sheet]
153
+ row,col = normalize(row,col)
154
+ if celltype(row,col,sheet) == :date
155
+ yyyy,mm,dd = @cell[sheet]["#{row},#{col}"].split('-')
156
+ begin
157
+ return Date.new(yyyy.to_i,mm.to_i,dd.to_i)
158
+ rescue ArgumentError
159
+ raise "Invalid date parameter: #{yyyy}, #{mm}, #{dd}"
160
+ end
161
+ elsif celltype(row,col,sheet) == :datetime
162
+ begin
163
+ date_part,time_part = @cell[sheet]["#{row},#{col}"].split(' ')
164
+ yyyy,mm,dd = date_part.split('-')
165
+ hh,mi,ss = time_part.split(':')
166
+ return DateTime.civil(yyyy.to_i,mm.to_i,dd.to_i,hh.to_i,mi.to_i,ss.to_i)
167
+ rescue ArgumentError
168
+ raise "Invalid date parameter: #{yyyy}, #{mm}, #{dd}, #{hh}, #{mi}, #{ss}"
169
+ end
170
+ end
171
+ return @cell[sheet]["#{row},#{col}"]
172
+ end
173
+
174
+ # returns the type of a cell:
175
+ # * :float
176
+ # * :string
177
+ # * :date
178
+ # * :percentage
179
+ # * :formula
180
+ # * :time
181
+ # * :datetime
182
+ def celltype(row, col, sheet=nil)
183
+ sheet = @default_sheet unless sheet
184
+ read_cells(sheet) unless @cells_read[sheet]
185
+ row,col = normalize(row,col)
186
+ if @formula[sheet]["#{row},#{col}"]
187
+ return :formula
188
+ else
189
+ @cell_type[sheet]["#{row},#{col}"]
190
+ end
191
+ end
192
+
193
+ # Returns the formula at (row,col).
194
+ # Returns nil if there is no formula.
195
+ # The method #formula? checks if there is a formula.
196
+ def formula(row,col,sheet=nil)
197
+ sheet = @default_sheet unless sheet
198
+ read_cells(sheet) unless @cells_read[sheet]
199
+ row,col = normalize(row,col)
200
+ if @formula[sheet]["#{row},#{col}"] == nil
201
+ return nil
202
+ else
203
+ return @formula[sheet]["#{row},#{col}"]
204
+ end
205
+ end
206
+
207
+ # true, if there is a formula
208
+ def formula?(row,col,sheet=nil)
209
+ sheet = @default_sheet unless sheet
210
+ read_cells(sheet) unless @cells_read[sheet]
211
+ row,col = normalize(row,col)
212
+ formula(row,col) != nil
213
+ end
214
+
215
+ # returns each formula in the selected sheet as an array of elements
216
+ # [row, col, formula]
217
+ def formulas(sheet=nil)
218
+ theformulas = Array.new
219
+ sheet = @default_sheet unless sheet
220
+ read_cells(sheet) unless @cells_read[sheet]
221
+ first_row(sheet).upto(last_row(sheet)) {|row|
222
+ first_column(sheet).upto(last_column(sheet)) {|col|
223
+ if formula?(row,col,sheet)
224
+ f = [row, col, formula(row,col,sheet)]
225
+ theformulas << f
226
+ end
227
+ }
228
+ }
229
+ theformulas
230
+ end
231
+
232
+ # returns all values in this row as an array
233
+ # row numbers are 1,2,3,... like in the spreadsheet
234
+ def row(rownumber,sheet=nil)
235
+ sheet = @default_sheet unless sheet
236
+ read_cells(sheet) unless @cells_read[sheet]
237
+ result = []
238
+ tmp_arr = []
239
+ @cell[sheet].each_pair {|key,value|
240
+ y,x = key.split(',')
241
+ x = x.to_i
242
+ y = y.to_i
243
+ if y == rownumber
244
+ tmp_arr[x] = value
245
+ end
246
+ }
247
+ result = tmp_arr[1..-1]
248
+ while result[-1] == nil
249
+ result = result[0..-2]
250
+ end
251
+ result
252
+ end
253
+
254
+ # true, if the cell is empty
255
+ def empty?(row, col, sheet=nil)
256
+ value = cell(row, col, sheet)
257
+ return true unless value
258
+ return false if value.class == Date # a date is never empty
259
+ return false if value.class == Float
260
+ return false if celltype(row,col,sheet) == :time
261
+ value.empty?
262
+ end
263
+
264
+ # returns all values in this column as an array
265
+ # column numbers are 1,2,3,... like in the spreadsheet
266
+ #--
267
+ #TODO: refactoring nach GenericSpreadsheet?
268
+ def column(columnnumber, sheet=nil)
269
+ if columnnumber.class == String
270
+ columnnumber = GenericSpreadsheet.letter_to_number(columnnumber)
271
+ end
272
+ sheet = @default_sheet unless sheet
273
+ read_cells(sheet) unless @cells_read[sheet]
274
+ result = []
275
+ first_row(sheet).upto(last_row(sheet)) do |row|
276
+ result << cell(row,columnnumber,sheet)
277
+ end
278
+ result
279
+ end
280
+
281
+ # sets the cell to the content of 'value'
282
+ # a formula can be set in the form of '=SUM(...)'
283
+ def set_value(row,col,value,sheet=nil)
284
+ sheet = @default_sheet unless sheet
285
+ raise RangeError, "sheet not set" unless sheet
286
+ #@@ Set and pass sheet_no
287
+ begin
288
+ sheet_no = sheets.index(sheet)+1
289
+ rescue
290
+ raise RangeError, "invalid sheet '"+sheet.to_s+"'"
291
+ end
292
+ row,col = normalize(row,col)
293
+ @gs.add_to_cell_roo(row,col,value,sheet_no)
294
+ end
295
+
296
+ # returns the first non-empty row in a sheet
297
+ def first_row(sheet=nil)
298
+ sheet = @default_sheet unless sheet
299
+ unless @first_row[sheet]
300
+ sheet_no = sheets.index(sheet) + 1
301
+ @first_row[sheet], @last_row[sheet], @first_column[sheet], @last_column[sheet] = @gs.oben_unten_links_rechts(sheet_no)
302
+ end
303
+ return @first_row[sheet]
304
+ end
305
+
306
+ # returns the last non-empty row in a sheet
307
+ def last_row(sheet=nil)
308
+ sheet = @default_sheet unless sheet
309
+ unless @last_row[sheet]
310
+ sheet_no = sheets.index(sheet) + 1
311
+ @first_row[sheet], @last_row[sheet], @first_column[sheet], @last_column[sheet] = @gs.oben_unten_links_rechts(sheet_no)
312
+ end
313
+ return @last_row[sheet]
314
+ end
315
+
316
+ # returns the first non-empty column in a sheet
317
+ def first_column(sheet=nil)
318
+ sheet = @default_sheet unless sheet
319
+ unless @first_column[sheet]
320
+ sheet_no = sheets.index(sheet) + 1
321
+ @first_row[sheet], @last_row[sheet], @first_column[sheet], @last_column[sheet] = @gs.oben_unten_links_rechts(sheet_no)
322
+ end
323
+ return @first_column[sheet]
324
+ end
325
+
326
+ # returns the last non-empty column in a sheet
327
+ def last_column(sheet=nil)
328
+ sheet = @default_sheet unless sheet
329
+ unless @last_column[sheet]
330
+ sheet_no = sheets.index(sheet) + 1
331
+ @first_row[sheet], @last_row[sheet], @first_column[sheet], @last_column[sheet] = @gs.oben_unten_links_rechts(sheet_no)
332
+ end
333
+ return @last_column[sheet]
334
+ end
335
+
336
+ private
337
+
338
+ # read all cells in a sheet
339
+ def read_cells(sheet=nil)
340
+ sheet = @default_sheet unless sheet
341
+ raise RangeError, "illegal sheet <#{sheet}>" unless sheets.index(sheet)
342
+ sheet_no = sheets.index(sheet)+1
343
+ doc = @gs.fulldoc(sheet_no)
344
+ (doc/"gs:cell").each {|item|
345
+ row = item['row']
346
+ col = item['col']
347
+ value = item['inputvalue']
348
+ numericvalue = item['numericvalue']
349
+ if value[0,1] == '='
350
+ formula = value
351
+ else
352
+ formula = nil
353
+ end
354
+ @cell_type[sheet] = {} unless @cell_type[sheet]
355
+ if formula
356
+ ty = :formula
357
+ if numeric?(numericvalue)
358
+ value = numericvalue.to_f
359
+ else
360
+ value = numericvalue
361
+ end
362
+ elsif Google.date?(value)
363
+ ty = :date
364
+ elsif Google.datetime?(value)
365
+ ty = :datetime
366
+ elsif numeric?(value) # or o.class ???
367
+ ty = :float
368
+ value = value.to_f
369
+ elsif Google.time?(value)
370
+ ty = :time
371
+ value = Google.timestring_to_seconds(value)
372
+ else
373
+ ty = :string
374
+ end
375
+ key = "#{row},#{col}"
376
+ @cell[sheet] = {} unless @cell[sheet]
377
+ if ty == :date
378
+ dd,mm,yyyy = value.split('/')
379
+ @cell[sheet][key] = sprintf("%04d-%02d-%02d",yyyy.to_i,mm.to_i,dd.to_i)
380
+ elsif ty == :datetime
381
+ date_part,time_part = value.split(' ')
382
+ dd,mm,yyyy = date_part.split('/')
383
+ hh,mi,ss = time_part.split(':')
384
+ @cell[sheet][key] = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
385
+ yyyy.to_i,mm.to_i,dd.to_i,hh.to_i,mi.to_i,ss.to_i)
386
+ else
387
+ @cell[sheet][key] = value unless value == "" or value == nil
388
+ end
389
+ @cell_type[sheet][key] = ty # Openoffice.oo_type_2_roo_type(vt)
390
+ @formula[sheet] = {} unless @formula[sheet]
391
+ @formula[sheet][key] = formula if formula
392
+ }
393
+ @cells_read[sheet] = true
394
+ end
395
+
396
+ def numeric?(string)
397
+ string =~ /^[0-9]+[\.]*[0-9]*$/
398
+ end
399
+
400
+ # convert string DD/MM/YYYY into a Date-object
401
+ #TODO: was ist mit verschiedenen Typen der Datumseingabe bei Google?
402
+ def Google.to_date(string)
403
+ if string.strip =~ /^([0-9]+)\/([0-9]+)\/([0-9]+)$/
404
+ return Date.new($3.to_i,$2.to_i,$1.to_i)
405
+ else
406
+ return nil
407
+ end
408
+ end
409
+
410
+
411
+ end # class