hmcgowan-roo 1.2.4

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 (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