roo 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,17 @@
1
+ * 7 enhancements:
2
+ * robustness: Exception if no default_sheet was set
3
+ * new method reload() implemented
4
+ * about 15 % more method documentation
5
+ * optimization: huge increase of speed (no need to use fixed borders anymore)
6
+ * added the method 'formulas' which gives you all formulas in a spreadsheet
7
+ * added the method 'set' which can set cells to a certain value
8
+ * added the method 'to_yaml' which can produce output for importing in a (rails) database
9
+ * 4 bugfixes
10
+ * ..row_as_letter methods were nonsense - removed
11
+ * @cells_read should be reset if the default_sheet is changed
12
+ * error in excel-part: strings are now converted to utf-8 (the parsexcel-gem gave me an error with my test data, which could not converted to .to_s using latin1 encoding)
13
+ * fixed bug when default_sheet is changed
14
+
1
15
  == 0.3.0 2007-06-20
2
16
  * 1 enhancement:
3
17
  * Openoffice: formula support
data/Manifest.txt CHANGED
@@ -8,6 +8,7 @@ lib/roo/version.rb
8
8
  lib/roo/openoffice.rb
9
9
  lib/roo/excel.rb
10
10
  lib/roo/google.rb
11
+ lib/roo/spreadsheetparser.rb
11
12
  scripts/txt2html
12
13
  setup.rb
13
14
  test/test_helper.rb
data/Rakefile CHANGED
@@ -79,6 +79,7 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
79
79
  ['rubyzip', '>= 0.9.1'],
80
80
  ['hpricot', '>= 0.5'],
81
81
  ['hoe', '>= 0.0.0'],
82
+ ['llip', '>= 0.0.1'],
82
83
  ]
83
84
  #p.spec_extras = {} # A hash of extra values to set in the gemspec.
84
85
  end
data/lib/roo/excel.rb CHANGED
@@ -4,8 +4,13 @@ require 'parseexcel'
4
4
  class Excel < Openoffice
5
5
 
6
6
  def initialize(filename)
7
+ if filename[-4..-1] != ".xls"
8
+ warn "are you sure, this is an excel file?"
9
+ end
10
+ @filename = filename
7
11
  @workbook = Spreadsheet::ParseExcel.parse(filename)
8
12
  @default_sheet = nil
13
+ @first_row = @last_row = @first_column = @last_column = nil
9
14
  end
10
15
 
11
16
  # TODO: waiting for
@@ -26,16 +31,19 @@ class Excel < Openoffice
26
31
  # im Excel-Bereich muesste man wahrscheinlich intern mit Nummern arbeiten
27
32
  # von aussen arbeite ich mit (1,2,3... intern wird Index 0,1,2,...
28
33
  # verwendet.
29
-
30
34
  def default_sheet=(n)
31
35
  unless n.kind_of?(Fixnum)
32
36
  fail ArgumentError.new("Number expected")
33
37
  end
34
38
  @default_sheet = n-1
39
+ @first_row = @last_row = @first_column = @last_column = nil
40
+ @cells_read = false
35
41
  end
36
42
 
43
+ # returns the content of a cell. The upper left corner is (1,1) or ('A',1)
37
44
  def cell(row,col)
38
45
  row,col = normalize(row,col)
46
+ default_sheet_check
39
47
  worksheet = @workbook.worksheet(@default_sheet)
40
48
  skip = 0
41
49
  line = 1
@@ -45,23 +53,23 @@ class Excel < Openoffice
45
53
  return nil
46
54
  end
47
55
  cell = row_par.at(col-1)
48
- # p "celltype: "
49
- # p cell.type
50
56
  return nil unless cell
51
57
  case cell.type
52
58
  when :numeric then return cell.to_f
53
- when :text then return cell.to_s('latin1')
59
+ when :text then return cell.to_s('utf-8')
54
60
  when :date then return cell.date
55
61
  else
56
- return cell.to_s
62
+ return cell.to_s('utf-8')
57
63
  end
58
64
  end
59
65
  line += 1
60
66
  }
61
67
  end
62
68
 
69
+ # returns the type of a cell: "float", "string", "date"
63
70
  def celltype(row,col)
64
71
  row,col = normalize(row,col)
72
+ default_sheet_check
65
73
  worksheet = @workbook.worksheet(@default_sheet)
66
74
  skip = 0
67
75
  line = 1
@@ -80,17 +88,20 @@ class Excel < Openoffice
80
88
  }
81
89
  end
82
90
 
91
+ # return this row a an array off cells
83
92
  def row(rownumber)
93
+ default_sheet_check
84
94
  worksheet = @workbook.worksheet(@default_sheet)
85
95
  therow = worksheet.row(rownumber-1)
86
96
  result = []
87
97
  therow.each {|cell|
88
98
  case cell.type
89
99
  when :numeric then result << cell.to_i
90
- when :text then result << cell.to_s('latin1')
100
+ when :text then result << cell.to_s('utf-8')
91
101
  when :date then result << cell.date
92
102
  else
93
- return result << cell.to_s
103
+ #p cell.type
104
+ return result << cell.to_s('utf-8')
94
105
  end
95
106
 
96
107
  #result << cell.value
@@ -98,42 +109,51 @@ class Excel < Openoffice
98
109
  return result
99
110
  end
100
111
 
112
+ # returns the first non empty column
101
113
  def first_column
114
+ return @first_column if @first_column
102
115
  fr, lr, fc, lc = get_firsts_lasts
103
116
  fc
104
117
  end
105
118
 
119
+ # returns the last non empty column
106
120
  def last_column
121
+ return @last_column if @last_column
107
122
  fr, lr, fc, lc = get_firsts_lasts
108
123
  lc
109
124
  end
110
125
 
126
+ # returns the first non empty row
111
127
  def first_row
128
+ return @first_row if @first_row
112
129
  fr, lr, fc, lc = get_firsts_lasts
113
130
  fr
114
131
  end
115
132
 
133
+ # returns the last non empty row
116
134
  def last_row
135
+ return @last_row if @last_row
117
136
  fr, lr, fc, lc = get_firsts_lasts
118
137
  lr
119
138
  end
120
139
 
140
+ # true if a cell is empty
121
141
  def empty?(row, col)
122
142
  row,col = normalize(row,col)
123
143
  return true if row < first_row || row > last_row || col < first_column || col > last_column
124
- # read_cells unless @cells_read
125
144
  return true unless cell(row, col)
126
- # p celltype(row,col)
127
- #p cell(row,col)
128
145
  return true if celltype(row, col) == "string" && cell(row, col) == ""
129
- #when :text then return cell.to_s('latin1')
130
- # p celltype(row,col)
131
- # return true if cell(row, col) == ""
132
146
  false
133
147
  end
134
148
 
135
149
  private
136
150
 
151
+ # check if default_sheet was set
152
+ def default_sheet_check
153
+ raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
154
+ end
155
+
156
+ # determine the first and last boundaries
137
157
  def get_firsts_lasts
138
158
  fr = fc = 999_999
139
159
  lr = lc = -999_999
@@ -159,6 +179,10 @@ private
159
179
  end
160
180
  line += 1
161
181
  }
182
+ @first_row = fr
183
+ @last_row = lr
184
+ @first_column = fc
185
+ @last_column = lc
162
186
  return fr, lr, fc, lc
163
187
  end
164
188
 
@@ -4,18 +4,18 @@ require 'rexml/document'
4
4
  require 'fileutils'
5
5
  require 'zip/zipfilesystem'
6
6
  require 'date'
7
+ require 'llip'
7
8
 
8
- class Fixnum
9
- def as_letter
10
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[self-1,1]
11
- end
12
- end
9
+ require 'lib/roo/spreadsheetparser'
13
10
 
14
11
  class Openoffice
15
12
 
16
13
  @@nr = 0
17
14
 
18
15
  def initialize(filename)
16
+ if filename[-4..-1] != ".ods"
17
+ warn "are you sure, this is an openoffice file?"
18
+ end
19
19
  @cells_read = false
20
20
  @filename = filename
21
21
  @tmpdir = "oo_"+$$.to_s
@@ -31,13 +31,16 @@ class Openoffice
31
31
  @cell = Hash.new
32
32
  @cell_type = Hash.new
33
33
  @formula = Hash.new
34
- if DateTime.now > Date.new(2007,6,21)
34
+ # if ENV["roo_local"] != "thomas-p"
35
35
  FileUtils::rm_r(@tmpdir)
36
- end
36
+ # end
37
37
  @default_sheet = nil
38
+ @first_column = @last_column = nil
39
+ @first_row = @last_row = nil
38
40
  end
39
41
 
40
42
  # reopens and read a spreadsheet document
43
+ if false
41
44
  def reload
42
45
  @cells_read = false
43
46
  @tmpdir = "oo_"+$$.to_s
@@ -52,9 +55,20 @@ class Openoffice
52
55
  @cell_type = Hash.new
53
56
  FileUtils::rm_r(@tmpdir)
54
57
  @default_sheet = nil
58
+ @first_column = @last_column = nil
59
+ @first_row = @last_row = nil
60
+ end
61
+ end
62
+
63
+ def reload
64
+ default_sheet = @default_sheet
65
+ initialize(@filename)
66
+ self.default_sheet = default_sheet
67
+ @first_row = @last_row = @first_column = @last_column = nil
68
+
55
69
  end
56
70
 
57
- # return the content of a spreadsheet-cell
71
+ # returns the content of a spreadsheet-cell
58
72
  # (1,1) is the upper left corner
59
73
  # (1,1), (1,'A'), ('A',1), ('a',1) all refers to the
60
74
  # cell at first line, first row
@@ -69,6 +83,7 @@ class Openoffice
69
83
  end
70
84
 
71
85
  # returns the formula at (row,col)
86
+ # nil if there is no formula
72
87
  def formula(row,col)
73
88
  read_cells unless @cells_read
74
89
  row,col = normalize(row,col)
@@ -79,21 +94,33 @@ class Openoffice
79
94
  end
80
95
  end
81
96
 
97
+ # true, if there is a formula
82
98
  def formula?(row,col)
99
+ read_cells unless @cells_read
100
+ row,col = normalize(row,col)
83
101
  formula(row,col) != nil
84
102
  end
85
103
 
104
+ # set a cell to a certain value
105
+ # (this will not be saved back to the spreadsheet file!)
86
106
  def set(row,col,value)
87
- puts "setze zelle(#{row},#{col})"
88
- @cell["#{row},#{col}"] = value
107
+ row,col = normalize(row,col)
108
+ set_value(row,col,value)
109
+ if value.class == Fixnum
110
+ set_type(row,col,:float)
111
+ elsif value.class == String
112
+ set_type(row,col,:string)
113
+ elsif value.class == Float
114
+ set_type(row,col,:string)
115
+ else
116
+ raise ArgumentError, "Typ fuer "+value.to_s+" nicht gesetzt"
117
+ end
89
118
  end
90
119
 
91
120
  # returns the open-office type of a cell
92
121
  def celltype(row,col)
93
122
  read_cells unless @cells_read
94
123
  row,col = normalize(row,col)
95
-
96
- # p @formula["#{row},#{col}"]
97
124
  if @formula["#{row},#{col}"]
98
125
  return :formula
99
126
  else
@@ -101,8 +128,7 @@ class Openoffice
101
128
  end
102
129
  end
103
130
 
104
-
105
- # returns an array of sheets in the spreadsheet
131
+ # returns an array of sheet names in the spreadsheet
106
132
  def sheets
107
133
  return_sheets = []
108
134
  oo_document_count = 0
@@ -130,11 +156,17 @@ class Openoffice
130
156
  # set the working sheet in the document
131
157
  def default_sheet=(s)
132
158
  @default_sheet = s
159
+ @first_row = @last_row = @first_column = @last_column = nil
160
+ @cells_read = false
161
+ @cell = Hash.new
162
+ @cell_type = Hash.new
163
+ @formula = Hash.new
133
164
  end
134
165
 
135
166
  # version of the openoffice document
167
+ # at 2007 this is always "1.0"
136
168
  def officeversion
137
- read_cells unless @cells_read
169
+ read_cells(:ignore_default_sheet => true) unless @cells_read
138
170
  @officeversion
139
171
  end
140
172
 
@@ -152,7 +184,6 @@ class Openoffice
152
184
  result = []
153
185
  tmp_arr = []
154
186
  @cell.each_pair {|key,value|
155
-
156
187
  y,x = key.split(',')
157
188
  x = x.to_i
158
189
  y = y.to_i
@@ -170,81 +201,86 @@ class Openoffice
170
201
  # returns the number of the last non-empty row
171
202
  def last_row
172
203
  read_cells unless @cells_read
173
- result = 0
204
+ if @last_row
205
+ return @last_row
206
+ end
207
+ impossible_value = 0
208
+ result = impossible_value
174
209
  @cell.each_pair {|key,value|
175
210
  y,x = key.split(',')
176
211
  y = y.to_i
177
212
  result = [result, y].max if value
178
213
  }
214
+ result = nil if result == impossible_value
215
+ @last_row = result
179
216
  result
180
217
  end
181
218
 
182
219
  # returns the number of the last non-empty column
183
220
  def last_column
184
221
  read_cells unless @cells_read
185
- result = 0
222
+ if @last_column
223
+ return @last_column
224
+ end
225
+ impossible_value = 0
226
+ result = impossible_value
186
227
  @cell.each_pair {|key,value|
187
228
  y,x = key.split(',')
188
229
  x = x.to_i
189
230
  result = [result, x].max if value
190
231
  }
232
+ result = nil if result == impossible_value
233
+ @last_column = result
191
234
  result
192
235
  end
193
236
 
194
237
  # returns the number of the first non-empty row
195
238
  def first_row
196
239
  read_cells unless @cells_read
197
- result = 999_999 # more than a spreadsheet can hold
240
+ if @first_row
241
+ return @first_row
242
+ end
243
+ impossible_value = 999_999 # more than a spreadsheet can hold
244
+ result = impossible_value
198
245
  @cell.each_pair {|key,value|
199
246
  y,x = key.split(',')
200
247
  y = y.to_i
201
248
  result = [result, y].min if value
202
249
  }
250
+ result = nil if result == impossible_value
251
+ @first_row = result
203
252
  result
204
253
  end
205
254
 
206
255
  # returns the number of the first non-empty column
207
256
  def first_column
208
257
  read_cells unless @cells_read
209
- result = 999_999 # more than a spreadsheet can hold
258
+ if @first_column
259
+ return @first_column
260
+ end
261
+ impossible_value = 999_999 # more than a spreadsheet can hold
262
+ result = impossible_value
210
263
  @cell.each_pair {|key,value|
211
264
  y,x = key.split(',')
212
265
  x = x.to_i
213
266
  result = [result, x].min if value
214
267
  }
268
+ result = nil if result == impossible_value
269
+ @first_column = result
215
270
  result
216
271
  end
217
272
 
273
+ # first non-empty column as a letter
218
274
  def first_column_as_letter
219
- number_to_letter(first_column)
275
+ Openoffice.number_to_letter(first_column)
220
276
  end
221
277
 
278
+ # last non-empty column as a letter
222
279
  def last_column_as_letter
223
- number_to_letter(last_column)
224
- end
225
-
226
- def first_row_as_letter
227
- number_to_letter(first_row)
228
- end
229
-
230
- def last_row_as_letter
231
- number_to_letter(last_row)
232
- end
233
-
234
- def as_letter(n)
235
- number_to_letter(last_row)
236
- end
237
-
238
- def number_to_letter(n)
239
- letters=""
240
- while n > 0
241
- num = n%26
242
- letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[num-1,1] + letters
243
- n = n.div(26)
244
- end
245
- letters
280
+ Openoffice.number_to_letter(last_column)
246
281
  end
247
282
 
283
+ # true if cell is empty
248
284
  def empty?(row, col)
249
285
  read_cells unless @cells_read
250
286
  return true unless cell(row, col)
@@ -252,25 +288,72 @@ class Openoffice
252
288
  false
253
289
  end
254
290
 
255
- def Openoffice.letter_to_number(letters)
256
- result = 0
257
- while letters && letters.length > 0
258
- character = letters[0,1].upcase
259
- num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character)+1
260
- result = result * 26 + num
261
- letters = letters[1..-1]
262
- end
263
- result
264
- end
265
-
291
+ =begin
292
+ # save spreadsheet
266
293
  def save
267
294
  42
268
295
  end
296
+ =end
297
+
298
+ # evaluate the formula at this cell
299
+ # experimental: DO NOT USE THIS!
300
+ def solve(row,col)
301
+ parser = SpreadsheetParser.new
302
+ visitor = Visitor.new
303
+ #puts cell(row,col)
304
+ puts formula(row,col)
305
+ formula = formula(row,col)[1..-1] # .downcase
306
+ puts formula
307
+ #eval formula
308
+ #parser.parse(formula)
309
+ parser.parse(formula).accept(visitor)
310
+ end
311
+
312
+ # returns each formula in the selected sheet as an array of elements
313
+ # [row, col, formula]
314
+ def formulas
315
+ theformulas = Array.new
316
+ read_cells unless @cells_read
317
+ first_row.upto(last_row) {|row|
318
+ first_column.upto(last_column) {|col|
319
+ if formula?(row,col)
320
+ f = [row, col, formula(row,col)]
321
+ theformulas << f
322
+ end
323
+ }
324
+ }
325
+ theformulas
326
+ end
327
+
328
+ # returns a rectangular area (default: all cells) as yaml-output
329
+ # you can add additional attributes with the prefix parameter like:
330
+ # oo.to_yaml({"file"=>"flightdata_2007-06-26", "sheet" => "1"})
331
+ def to_yaml(prefix={}, from_row=nil, from_column=nil, to_row=nil, to_column=nil)
332
+ result = "--- \n"
333
+ (from_row||first_row).upto(to_row||last_row) do |row|
334
+ (from_column||first_column).upto(to_column||last_column) do |col|
335
+ unless self.empty?(row,col)
336
+ result << "cell_#{row}_#{col}: \n"
337
+ prefix.each {|k,v|
338
+ result << " #{k}: #{v} \n"
339
+ }
340
+ result << " row: #{row} \n"
341
+ result << " col: #{col} \n"
342
+ result << " celltype: #{self.celltype(row,col)} \n"
343
+ result << " value: #{self.cell(row,col)} \n"
344
+ end
345
+ end
346
+ end
347
+ result
348
+ end
269
349
 
270
350
  private
271
351
 
272
352
  # read all cells in the selected sheet
273
- def read_cells
353
+ def read_cells(*args)
354
+ if :ignore_default_sheet == false
355
+ raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
356
+ end
274
357
  oo_document_count = 0
275
358
  @doc.each_element do |oo_document|
276
359
  @officeversion = oo_document.attributes['version']
@@ -278,35 +361,24 @@ private
278
361
  oo_element_count = 0
279
362
  oo_document.each_element do |oo_element|
280
363
  oo_element_count += 1
281
- # p oo_element.name
282
364
  if oo_element.name == "body"
283
- # puts "Body gefunden "
284
365
  oo_element.each_element do |be|
285
- # p be.name
286
366
  if be.name == "spreadsheet"
287
367
  be.each_element do |se|
288
- # p se
289
368
  if se.name == "table"
290
369
  if se.attributes['name']==@default_sheet
291
370
 
292
371
  x=1
293
372
  y=1
294
- # puts "table gefunden"
295
- #se.each_element
296
373
  se.each_element do |te|
297
- # p te.name
298
374
  if te.name == "table-column"
299
- # p te.attributes
300
375
  rep = te.attributes["number-columns-repeated"]
301
- # p "rep = "+rep.to_s
302
376
  elsif te.name == "table-row"
303
377
  if te.attributes['number-rows-repeated']
304
378
  skip_y = te.attributes['number-rows-repeated'].to_i
305
379
  y = y + skip_y - 1 # minus 1 because this line will be counted as a line element
306
380
  end
307
- # p te
308
381
  te.each_element do |tr|
309
- # p tr
310
382
  if tr.name == 'table-cell'
311
383
  skip = tr.attributes['number-columns-repeated']
312
384
  formula = tr.attributes['formula']
@@ -329,14 +401,7 @@ private
329
401
  if @cell_type["#{y},#{x+i}"] == 'float'
330
402
  @cell["#{y},#{x+i}"] = v.to_f
331
403
  elsif @cell_type["#{y},#{x+i}"] == 'string'
332
- # puts "in string zweig..."
333
- #tr.each_element do |str|
334
- # if str.name == 'p'
335
- # @cell["#{y},#{x+i}"] = str.text
336
- # end
337
- #end
338
404
  @cell["#{y},#{x+i}"] = v
339
-
340
405
  elsif @cell_type["#{y},#{x+i}"] == 'date'
341
406
  @cell["#{y},#{x+i}"] = tr.attributes['date-value']
342
407
  else
@@ -420,4 +485,36 @@ private
420
485
  return row,col
421
486
  end
422
487
 
423
- end
488
+ # convert a number to something like this: 'AB'
489
+ def Openoffice.number_to_letter(n)
490
+ letters=""
491
+ while n > 0
492
+ num = n%26
493
+ letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[num-1,1] + letters
494
+ n = n.div(26)
495
+ end
496
+ letters
497
+ end
498
+
499
+ # convert letters like 'AB' to a number
500
+ def Openoffice.letter_to_number(letters)
501
+ result = 0
502
+ while letters && letters.length > 0
503
+ character = letters[0,1].upcase
504
+ num = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(character)+1
505
+ result = result * 26 + num
506
+ letters = letters[1..-1]
507
+ end
508
+ result
509
+ end
510
+
511
+
512
+ def set_value(row,col,value)
513
+ @cell["#{row},#{col}"] = value
514
+ end
515
+
516
+ def set_type(row,col,type)
517
+ @cell_type["#{row},#{col}"] = type
518
+ end
519
+
520
+ end # class
@@ -0,0 +1,91 @@
1
+ =begin
2
+ This is experimental. Please do not use it. IT WILL NOT WORK
3
+ I don't know if i will extend the work on the evaluation of formulas.
4
+ You can access all formulas as a string and do whatever you want with this.
5
+ =end
6
+
7
+ require 'llip'
8
+ require 'llip/visitable'
9
+
10
+ class Formula
11
+ include LLIP::Visitable
12
+
13
+ attr_accessor :name
14
+ attr_accessor :params
15
+ end
16
+
17
+ class Param
18
+ include LLIP::Visitable
19
+
20
+ attr_accessor :column_name
21
+ end
22
+
23
+ class Visitor
24
+ def visit_formula(formula)
25
+ puts " -- " + formula.name
26
+ formula.params.each { |p| p.accept(self) }
27
+ end
28
+
29
+ def visit_param(param)
30
+ puts " |-- " + param.column_name
31
+ end
32
+ end
33
+
34
+ class SpreadsheetParser < LLIP::Parser
35
+ letters = ("A".."Z").to_a.join("|")
36
+
37
+ token :id, "(#{letters})+"
38
+
39
+ token :"(", '\('
40
+
41
+ token :")", '\)'
42
+
43
+ token :"[", '['
44
+ token :"]", ']'
45
+ token :".", '.'
46
+
47
+ num = (1..9).to_a.join("|")
48
+ token :num , "(#{num})(#{num}|0)*"
49
+
50
+ token :sep, ":"
51
+
52
+ scope :formula
53
+
54
+ production :formula do |p|
55
+ p.token(:id) do |result,scanner,parser|
56
+ result = Formula.new
57
+ result.name = scanner.current
58
+ puts "<"+result.name+">"
59
+ raise unless scanner.next == :"("
60
+ raise unless scanner.next == :"["
61
+ scanner.next
62
+ result.params = parser.parse_params
63
+ raise unless scanner.current == :"]"
64
+ raise unless scanner.current == :")"
65
+ scanner.next
66
+ result
67
+ end
68
+ end
69
+
70
+ production :params, :recursive do |p|
71
+ p.default do |scanner, parser|
72
+ []
73
+ end
74
+
75
+ p.token(:id) do |result, scanner, parser|
76
+ param = Param.new
77
+ param.column_name = scanner.current.to_s
78
+ raise unless scanner.next == :"."
79
+ raise unless scanner.next == :num
80
+ param.column_name += scanner.current.to_s
81
+ scanner.next
82
+ result << param
83
+ end
84
+
85
+ p.token(:sep) do |result,scanner,parser|
86
+ scanner.next
87
+ result
88
+ end
89
+ end
90
+
91
+ end
data/lib/roo/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Roo #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 3
4
+ MINOR = 4
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
data/test/numbers1.xls CHANGED
Binary file
data/test/test_roo.rb CHANGED
@@ -190,6 +190,7 @@ end
190
190
 
191
191
  assert_equal "thisisd9", oo.cell('d',9)
192
192
  assert_equal "thisisa11", oo.cell('a',11)
193
+ #assert_equal "lulua", oo.cell('b',10)
193
194
  end
194
195
 
195
196
  if GOOGLE
@@ -430,8 +431,6 @@ end
430
431
  assert_equal 5, oo.first_row
431
432
  assert_equal 'E', oo.last_column_as_letter
432
433
  assert_equal 14, oo.last_row
433
- assert_equal 'E', oo.first_row_as_letter
434
- assert_equal 'N', oo.last_row_as_letter
435
434
  if EXCEL
436
435
  #-- Excel
437
436
  oo = Excel.new(File.join("test","numbers1.xls"))
@@ -441,8 +440,6 @@ end
441
440
  assert_equal 5, oo.first_row
442
441
  assert_equal 'E', oo.last_column_as_letter
443
442
  assert_equal 14, oo.last_row
444
- assert_equal 'E', oo.first_row_as_letter
445
- assert_equal 'N', oo.last_row_as_letter
446
443
  end
447
444
  end
448
445
 
@@ -537,14 +534,22 @@ end
537
534
  end
538
535
 
539
536
  def test_reload
540
- oo = Openoffice.new(File.join("test","numbers1.ods"))
541
- oo.default_sheet = oo.sheets.first
542
- assert_equal 1, oo.cell(1,1)
537
+ if OPENOFFICE
538
+ oo = Openoffice.new(File.join("test","numbers1.ods"))
539
+ oo.default_sheet = oo.sheets.first
540
+ assert_equal 1, oo.cell(1,1)
543
541
 
544
- if DateTime.now < Date.new(2007, 6, 15)
545
- oo.reload
546
- assert_equal 2, oo.cell(1,1)
547
- end
542
+ oo.reload
543
+ assert_equal 1, oo.cell(1,1)
544
+ end
545
+ if EXCEL
546
+ oo = Excel.new(File.join("test","numbers1.xls"))
547
+ oo.default_sheet = 1 # oo.sheets.first
548
+ assert_equal 1, oo.cell(1,1)
549
+
550
+ oo.reload
551
+ assert_equal 1, oo.cell(1,1)
552
+ end
548
553
  end
549
554
 
550
555
  def test_bug_contiguous_cells
@@ -687,59 +692,167 @@ end
687
692
  end
688
693
  end
689
694
 
695
+ def myfunc(n)
696
+ puts "#{n} Euro"
697
+ end
690
698
 
691
699
  def test_formula
700
+ if OPENOFFICE
701
+ oo = Openoffice.new(File.join("test","formula.ods"))
702
+ oo.default_sheet = oo.sheets.first
703
+ assert_equal 1, oo.cell('A',1)
704
+ assert_equal 2, oo.cell('A',2)
705
+ assert_equal 3, oo.cell('A',3)
706
+ assert_equal 4, oo.cell('A',4)
707
+ assert_equal 5, oo.cell('A',5)
708
+ assert_equal 6, oo.cell('A',6)
709
+ assert_equal 21, oo.cell('A',7)
710
+ assert_equal :formula, oo.celltype('A',7)
711
+ assert_equal "=[Sheet2.A1]", oo.formula('C',7)
712
+ assert_nil oo.formula('A',6)
713
+ assert_equal [[7, 1, "=SUM([.A1:.A6])"],
714
+ [7, 2, "=SUM([.$A$1:.B6])"],
715
+ [7, 3, "=[Sheet2.A1]"],
716
+ [8, 2, "=SUM([.$A$1:.B7])"],
717
+ ], oo.formulas
692
718
 
693
- oo = Openoffice.new(File.join("test","formula.ods"))
694
- oo.default_sheet = oo.sheets.first
695
- assert_equal 1, oo.cell('A',1)
696
- assert_equal 2, oo.cell('A',2)
697
- assert_equal 3, oo.cell('A',3)
698
- assert_equal 4, oo.cell('A',4)
699
- assert_equal 5, oo.cell('A',5)
700
- assert_equal 6, oo.cell('A',6)
701
- assert_equal 21, oo.cell('A',7)
702
- assert_equal :formula, oo.celltype('A',7)
703
- #assert_equal "=SUM(A1:A6)", oo.formula('A',7)
704
- #assert_equal "=SUM(A1:A6)", oo.formula('B',7)
705
- #assert_equal "=SUM(A1:A6)", oo.formula('B',7)
706
- assert_equal "=[Sheet2.A1]", oo.formula('C',7)
707
- assert_nil oo.formula('A',6)
708
-
709
- oo = Openoffice.new(File.join("test","external1.ods"))
710
- # each spreadsheet, each row, each column
711
- oo.sheets.each {|sheet|
712
- oo.default_sheet = sheet
713
- oo.first_row.upto(oo.last_row) do |row|
714
- oo.first_column.upto(oo.last_column) do |col|
715
- value = oo.cell(row,col)
716
- # is it a formula?
717
- if oo.formula?(row,col)
718
- # formula
719
- puts oo.formula(row,col)
720
- # value
721
- puts value if value
722
- else
723
- puts value if value
719
+ if DateTime.now > Date.new(2007,6,25)
720
+ # setting a cell
721
+ oo.set('A',15, 41)
722
+ assert_equal 41, oo.cell('A',15)
723
+ oo.set('A',16, "41")
724
+ assert_equal "41", oo.cell('A',16)
725
+ oo.set('A',17, 42.5)
726
+ assert_equal 42.5, oo.cell('A',17)
727
+ end
728
+ if DateTime.now > Date.new(2007,6,30)
729
+ assert_equal 21, oo.solve('a',7)
730
+ end
731
+
732
+ oo = Openoffice.new(File.join("test","external1.ods"))
733
+ # each spreadsheet, each row, each column
734
+ oo.sheets.each {|sheet|
735
+ oo.default_sheet = sheet
736
+ if oo.first_row
737
+ oo.first_row.upto(oo.last_row) do |row|
738
+ oo.first_column.upto(oo.last_column) do |col|
739
+ value = oo.cell(row,col)
740
+ # is it a formula?
741
+ if oo.formula?(row,col)
742
+ # formula
743
+ puts oo.formula(row,col)
744
+ # value
745
+ puts value if value
746
+ else
747
+ puts value if value
748
+ end
749
+ end
724
750
  end
725
751
  end
726
- end
727
- }
728
- if false
729
- oo = Excel.new(File.join("test","formula.xls"))
730
- oo.default_sheet = 1 # oo.sheets.first
731
- assert_equal 1, oo.cell('A',1)
732
- assert_equal 2, oo.cell('A',2)
733
- assert_equal 3, oo.cell('A',3)
734
- assert_equal 4, oo.cell('A',4)
735
- assert_equal 5, oo.cell('A',5)
736
- assert_equal 6, oo.cell('A',6)
737
- assert_equal 21, oo.cell('A',7), oo.cell('A',7).to_yaml
738
- assert_equal :formula, oo.celltype('A',7)
739
- assert_equal "=SUM(A1:A6)", oo.formula('A',7)
740
- assert_equal "=SUM(A1:A6)", oo.formula('B',7)
741
- assert_nil oo.formula('A',6)
752
+ }
742
753
  end
754
+ end
755
+
756
+
757
+ def test_borders_sheets
758
+ if OPENOFFICE
759
+ oo = Openoffice.new(File.join("test","borders.ods"))
760
+ oo.default_sheet = oo.sheets[1]
761
+ assert_equal 6, oo.first_row
762
+ assert_equal 11, oo.last_row
763
+ assert_equal 4, oo.first_column
764
+ assert_equal 8, oo.last_column
743
765
 
766
+ oo.default_sheet = oo.sheets.first
767
+ #assert_nil oo.first_row
768
+ assert_equal 5, oo.first_row
769
+ #assert_nil oo.last_row
770
+ assert_equal 10, oo.last_row
771
+ assert_equal 3, oo.first_column
772
+ #assert_nil oo.first_column
773
+ assert_equal 7, oo.last_column
774
+ #assert_nil oo.last_column
775
+
776
+ oo.default_sheet = oo.sheets[2]
777
+ assert_equal 7, oo.first_row
778
+ assert_equal 12, oo.last_row
779
+ assert_equal 5, oo.first_column
780
+ assert_equal 9, oo.last_column
781
+ end
782
+ if EXCEL
783
+ oo = Excel.new(File.join("test","borders.xls"))
784
+ oo.default_sheet = 2 # oo.sheets[1]
785
+ assert_equal 6, oo.first_row
786
+ assert_equal 11, oo.last_row
787
+ assert_equal 4, oo.first_column
788
+ assert_equal 8, oo.last_column
789
+
790
+ oo.default_sheet = 1 # oo.sheets.first
791
+ #assert_nil oo.first_row
792
+ assert_equal 5, oo.first_row
793
+ #assert_nil oo.last_row
794
+ assert_equal 10, oo.last_row
795
+ assert_equal 3, oo.first_column
796
+ #assert_nil oo.first_column
797
+ assert_equal 7, oo.last_column
798
+ #assert_nil oo.last_column
799
+
800
+ oo.default_sheet = 3 # oo.sheets[2]
801
+ assert_equal 7, oo.first_row
802
+ assert_equal 12, oo.last_row
803
+ assert_equal 5, oo.first_column
804
+ assert_equal 9, oo.last_column
805
+ end
806
+
744
807
  end
808
+
809
+ def yaml_entry(row,col,type,value)
810
+ "cell_#{row}_#{col}: \n row: #{row} \n col: #{col} \n celltype: #{type} \n value: #{value} \n"
811
+ end
812
+
813
+ def test_to_yaml
814
+ if OPENOFFICE
815
+ oo = Openoffice.new(File.join("test","numbers1.ods"))
816
+ oo.default_sheet = oo.sheets.first
817
+ assert_equal "--- \n"+yaml_entry(5,1,"date","1961-11-21"), oo.to_yaml({}, 5,1,5,1)
818
+ assert_equal "--- \n"+yaml_entry(8,3,"string","thisisc8"), oo.to_yaml({}, 8,3,8,3)
819
+ assert_equal "--- \n"+yaml_entry(12,3,"float",43.0), oo.to_yaml({}, 12,3,12,3)
820
+ assert_equal \
821
+ "--- \n"+yaml_entry(12,3,"float",43.0) +
822
+ yaml_entry(12,4,"float",44.0) +
823
+ yaml_entry(12,5,"float",45.0), oo.to_yaml({}, 12,3,12)
824
+ assert_equal \
825
+ "--- \n"+yaml_entry(12,3,"float",43.0)+
826
+ yaml_entry(12,4,"float",44.0)+
827
+ yaml_entry(12,5,"float",45.0)+
828
+ yaml_entry(15,3,"float",43.0)+
829
+ yaml_entry(15,4,"float",44.0)+
830
+ yaml_entry(15,5,"float",45.0)+
831
+ yaml_entry(16,3,"string","dreiundvierzig")+
832
+ yaml_entry(16,4,"string","vierundvierzig")+
833
+ yaml_entry(16,5,"string","fuenfundvierzig"), oo.to_yaml({}, 12,3)
834
+ end
835
+ if EXCEL
836
+ oo = Excel.new(File.join("test","numbers1.xls"))
837
+ oo.default_sheet = 1
838
+ assert_equal "--- \n"+yaml_entry(5,1,"date","1961-11-21"), oo.to_yaml({}, 5,1,5,1)
839
+ assert_equal "--- \n"+yaml_entry(8,3,"string","thisisc8"), oo.to_yaml({}, 8,3,8,3)
840
+ assert_equal "--- \n"+yaml_entry(12,3,"float",43.0), oo.to_yaml({}, 12,3,12,3)
841
+ assert_equal \
842
+ "--- \n"+yaml_entry(12,3,"float",43.0) +
843
+ yaml_entry(12,4,"float",44.0) +
844
+ yaml_entry(12,5,"float",45.0), oo.to_yaml({}, 12,3,12)
845
+ assert_equal \
846
+ "--- \n"+yaml_entry(12,3,"float",43.0)+
847
+ yaml_entry(12,4,"float",44.0)+
848
+ yaml_entry(12,5,"float",45.0)+
849
+ yaml_entry(15,3,"float",43.0)+
850
+ yaml_entry(15,4,"float",44.0)+
851
+ yaml_entry(15,5,"float",45.0)+
852
+ yaml_entry(16,3,"string","dreiundvierzig")+
853
+ yaml_entry(16,4,"string","vierundvierzig")+
854
+ yaml_entry(16,5,"string","fuenfundvierzig"), oo.to_yaml({}, 12,3)
855
+ end
856
+ end
857
+
745
858
  end # class
data/website/index.html CHANGED
@@ -33,7 +33,7 @@
33
33
  <h1>roo</h1>
34
34
  <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/roo"; return false'>
35
35
  Get Version
36
- <a href="http://rubyforge.org/projects/roo" class="numbers">0.3.0</a>
36
+ <a href="http://rubyforge.org/projects/roo" class="numbers">0.4.0</a>
37
37
  </div>
38
38
  <h2>What</h2>
39
39
 
@@ -146,16 +146,49 @@
146
146
  <p>oo.celltype(row,col) returns :formula if there is a formula in this cell.</p>
147
147
 
148
148
 
149
- <p>oo.formula(row,col) returns the formula in this cell in a string variable.
150
- If there is no formula in this cell nil is return</p>
149
+ <p>oo.formula?(row,col) returns true if there is a formula</p>
150
+
151
+
152
+ <p>oo.formula(row,col) returns the formula in this cell in a string variable (like &#8221;=<acronym title="[.A1:.M13]">SUM</acronym>&#8221;). You can do whatever you want with this expression.
153
+ If there is no formula in this cell nil is returned.</p>
151
154
 
152
155
 
153
156
  <p>oo.cell(row,col) returns the computed result of the formula (as it was saved in the file, no recalculation is done in this Gem).</p>
154
157
 
155
158
 
159
+ <p>oo.formulas returns all formulas in the selected spreadsheet in an array like this:</p>
160
+
161
+
162
+ <p>Each entry consists of the elements row, col, formual.</p>
163
+
164
+
165
+ <p>Note: oo.cell(row,col) is the same for ordinary cells and formulas. So you can use the computated value of a formula. If you have to distinguish if a cell is a formula use #formula?</p>
166
+
167
+
156
168
  <p>Please note: formulas in Excel-Spreadsheets cannot be handled (this is another gem, see: &#8220;Thanks&#8221;)</p>
157
169
 
158
170
 
171
+ <h3><span class="caps">YAML</span>-Output</h3>
172
+
173
+
174
+ <p>You can generate <span class="caps">YAML</span>-Output from your spreadsheet data. The method is called:</p>
175
+
176
+
177
+ <p>oo.to_yaml # =&gt; produces <span class="caps">YAML</span> output from the entire default spreadsheet
178
+ oo.to_yaml({&#8220;myattribute1&#8221; =&gt; &#8220;myvalue1&#8221;, &#8220;myattribute2&#8221; =&gt; &#8220;myvalue2&#8221;)
179
+ # =&gt; <span class="caps">YAML</span> output with additional attributes
180
+ oo.to_yaml({..}, 2,10, 300,10) # =&gt; only the rectangle from row 2, column 10 to row 300, column 10 will be returned</p>
181
+
182
+
183
+ <p>If you omit one or more parameters the maximum boundaries of your spreadsheet will be used.</p>
184
+
185
+
186
+ <p>With the <span class="caps">YAML</span> output you can import your data in a Ruby on Rails application in a manner that spreadsheet data can accessed in a Rails application.</p>
187
+
188
+
189
+ <p>This is not limited to a Rails application &#8211; you can also do further evaluations with your data.</p>
190
+
191
+
159
192
  <h3>Using MS-Excel spreadsheets</h3>
160
193
 
161
194
 
@@ -169,11 +202,40 @@ Replace Openoffice with
169
202
  </code>
170
203
  </pre>
171
204
 
172
- <p>all methode are the same for OpenOffice and Excel-objects. The only difference
205
+ <p>All methode are the same for OpenOffice and Excel-objects. The only difference
173
206
  is the setting of the default-worksheet. OpenOffice uses the name of the worksheet whereas Excel needs the index of the worksheet (1,2,3,..).</p>
174
207
 
175
208
 
176
- <p>Formulas cannot be handled in Excel-spreadsheets.</p>
209
+ <p>Formulas can only be handled in OpenOffice-spreadsheets.</p>
210
+
211
+
212
+ <p>Features in OpenOffice and Excel:</p>
213
+
214
+
215
+ <table class="border:1px solid black">
216
+ <tr>
217
+ <td>feature</td>
218
+ <td>Open Office</td>
219
+ <td>Excel</td>
220
+ </tr>
221
+ <tr>
222
+ <td>default_sheet</td>
223
+ <td>as name</td>
224
+ <td>as number</td>
225
+ </tr>
226
+ <tr>
227
+ <td>formulas</td>
228
+ <td>yes</td>
229
+ <td>no</td>
230
+ </tr>
231
+ <tr>
232
+ <td>to_yaml</td>
233
+ <td>yes</td>
234
+ <td>yes</td>
235
+ </tr>
236
+ </table>
237
+
238
+
177
239
 
178
240
 
179
241
  <h2>Where is it used?</h2>
@@ -182,9 +244,17 @@ is the setting of the default-worksheet. OpenOffice uses the name of the workshe
182
244
  <p>How do you use roo? What are you doing with roo?</p>
183
245
 
184
246
 
247
+ <ul>
248
+ <li>The author of this gem uses roo for the generation of weekly reports which are (automatically) sent to his customers (Thomas Preymesser, Homepage: www.thopre.com, Blog: thopre.wordpress.com, email me: thopre@gmail.com)</li>
249
+ </ul>
250
+
251
+
185
252
  <p>If you have an interesting application where you use roo then write me a short description of your project and i will publish it here (write, if your email-address should be published or not).</p>
186
253
 
187
254
 
255
+ <p>Or you can write directly in the project wiki at <a href="http://roo.rubyforge.org/wiki/wiki.pl?Who's_Using_Roo">http://roo.rubyforge.org/wiki/wiki.pl?Who&#8217;s_Using_Roo</a></p>
256
+
257
+
188
258
  <p>If you don&#8217;t want to publish the details you can also write me an email and state, that it should not be published &#8211; i am just curious to hear, where it is used.</p>
189
259
 
190
260
 
@@ -233,7 +303,7 @@ is the setting of the default-worksheet. OpenOffice uses the name of the workshe
233
303
  <li>Dirk Huth f&uuml;rs Testen unter Windows</li>
234
304
  </ul>
235
305
  <p class="coda">
236
- <a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 20th June 2007<br>
306
+ <a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 26th June 2007<br>
237
307
  Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
238
308
  </p>
239
309
  </div>
data/website/index.txt CHANGED
@@ -93,13 +93,40 @@ Formulas in Openoffice-Spreadsheets can be handled.
93
93
 
94
94
  oo.celltype(row,col) returns :formula if there is a formula in this cell.
95
95
 
96
- oo.formula(row,col) returns the formula in this cell in a string variable.
97
- If there is no formula in this cell nil is return
96
+ oo.formula?(row,col) returns true if there is a formula
97
+
98
+ oo.formula(row,col) returns the formula in this cell in a string variable (like "=SUM([.A1:.M13])"). You can do whatever you want with this expression.
99
+ If there is no formula in this cell nil is returned.
98
100
 
99
101
  oo.cell(row,col) returns the computed result of the formula (as it was saved in the file, no recalculation is done in this Gem).
100
102
 
103
+ oo.formulas returns all formulas in the selected spreadsheet in an array like this:
104
+
105
+ [[1,2,"=SUM(.A1:.B1)"],
106
+ [1,3,"=SIN(.C3)"],
107
+ [1,4,"=COS(.D4)"]]
108
+
109
+ Each entry consists of the elements row, col, formual.
110
+
111
+ Note: oo.cell(row,col) is the same for ordinary cells and formulas. So you can use the computated value of a formula. If you have to distinguish if a cell is a formula use #formula?
112
+
101
113
  Please note: formulas in Excel-Spreadsheets cannot be handled (this is another gem, see: "Thanks")
102
114
 
115
+ h3. YAML-Output
116
+
117
+ You can generate YAML-Output from your spreadsheet data. The method is called:
118
+
119
+ oo.to_yaml # => produces YAML output from the entire default spreadsheet
120
+ oo.to_yaml({"myattribute1" => "myvalue1", "myattribute2" => "myvalue2")
121
+ # => YAML output with additional attributes
122
+ oo.to_yaml({..}, 2,10, 300,10) # => only the rectangle from row 2, column 10 to row 300, column 10 will be returned
123
+
124
+ If you omit one or more parameters the maximum boundaries of your spreadsheet will be used.
125
+
126
+ With the YAML output you can import your data in a Ruby on Rails application in a manner that spreadsheet data can accessed in a Rails application.
127
+
128
+ This is not limited to a Rails application - you can also do further evaluations with your data.
129
+
103
130
  h3. Using MS-Excel spreadsheets
104
131
 
105
132
  You can also access MS-Excel spreadsheat.
@@ -111,18 +138,32 @@ Replace Openoffice with
111
138
  </code>
112
139
  </pre>
113
140
 
114
- all methode are the same for OpenOffice and Excel-objects. The only difference
141
+ All methode are the same for OpenOffice and Excel-objects. The only difference
115
142
  is the setting of the default-worksheet. OpenOffice uses the name of the worksheet whereas Excel needs the index of the worksheet (1,2,3,..).
116
143
 
117
- Formulas cannot be handled in Excel-spreadsheets.
144
+ Formulas can only be handled in OpenOffice-spreadsheets.
145
+
146
+ Features in OpenOffice and Excel:
147
+
148
+ table(border:1px solid black).
149
+ |feature|Open Office|Excel|
150
+ |default_sheet|as name|as number|
151
+ |formulas|yes|no|
152
+ |to_yaml|yes|yes|
118
153
 
119
154
 
120
155
  h2. Where is it used?
121
156
 
122
157
  How do you use roo? What are you doing with roo?
123
158
 
159
+ * The author of this gem uses roo for the generation of weekly reports which are (automatically) sent to his customers (Thomas Preymesser, Homepage: www.thopre.com, Blog: thopre.wordpress.com, email me: thopre@gmail.com)
160
+
161
+
124
162
  If you have an interesting application where you use roo then write me a short description of your project and i will publish it here (write, if your email-address should be published or not).
125
163
 
164
+ Or you can write directly in the project wiki at "http://roo.rubyforge.org/wiki/wiki.pl?Who's_Using_Roo":http://roo.rubyforge.org/wiki/wiki.pl?Who's_Using_Roo
165
+
166
+
126
167
  If you don't want to publish the details you can also write me an email and state, that it should not be published - i am just curious to hear, where it is used.
127
168
 
128
169
  h2. Documentation
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: roo
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.0
7
- date: 2007-06-20 00:00:00 +02:00
6
+ version: 0.4.0
7
+ date: 2007-06-27 00:00:00 +02:00
8
8
  summary: roo can access the contents of OpenOffice-Spreadsheets
9
9
  require_paths:
10
10
  - lib
@@ -39,6 +39,7 @@ files:
39
39
  - lib/roo/openoffice.rb
40
40
  - lib/roo/excel.rb
41
41
  - lib/roo/google.rb
42
+ - lib/roo/spreadsheetparser.rb
42
43
  - scripts/txt2html
43
44
  - setup.rb
44
45
  - test/test_helper.rb
@@ -96,3 +97,12 @@ dependencies:
96
97
  - !ruby/object:Gem::Version
97
98
  version: "0.5"
98
99
  version:
100
+ - !ruby/object:Gem::Dependency
101
+ name: llip
102
+ version_requirement:
103
+ version_requirements: !ruby/object:Gem::Version::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 0.0.1
108
+ version: