rubyfromexcel 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/README +22 -0
  2. data/bin/rubyfromexcel +20 -0
  3. data/examples/create_and_test_examples.rb +37 -0
  4. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet1.rb +59 -0
  5. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet2.rb +9 -0
  6. data/examples/ruby-versions/array-formulas-ruby/specs/sheet1_rspec.rb +156 -0
  7. data/examples/ruby-versions/array-formulas-ruby/specs/sheet2_rspec.rb +8 -0
  8. data/examples/ruby-versions/array-formulas-ruby/spreadsheet.rb +9 -0
  9. data/examples/ruby-versions/complex-test-ruby/sheets/sheet1.rb +305 -0
  10. data/examples/ruby-versions/complex-test-ruby/sheets/sheet2.rb +147 -0
  11. data/examples/ruby-versions/complex-test-ruby/specs/sheet1_rspec.rb +876 -0
  12. data/examples/ruby-versions/complex-test-ruby/specs/sheet2_rspec.rb +412 -0
  13. data/examples/ruby-versions/complex-test-ruby/spreadsheet.rb +9 -0
  14. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet1.rb +9 -0
  15. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet2.rb +8 -0
  16. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet1_rspec.rb +16 -0
  17. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet2_rspec.rb +16 -0
  18. data/examples/ruby-versions/namedReferenceTest-ruby/spreadsheet.rb +9 -0
  19. data/examples/ruby-versions/pruning-ruby/sheets/sheet1.rb +11 -0
  20. data/examples/ruby-versions/pruning-ruby/sheets/sheet2.rb +14 -0
  21. data/examples/ruby-versions/pruning-ruby/sheets/sheet3.rb +7 -0
  22. data/examples/ruby-versions/pruning-ruby/specs/sheet1_rspec.rb +20 -0
  23. data/examples/ruby-versions/pruning-ruby/specs/sheet2_rspec.rb +20 -0
  24. data/examples/ruby-versions/pruning-ruby/specs/sheet3_rspec.rb +8 -0
  25. data/examples/ruby-versions/pruning-ruby/spreadsheet.rb +9 -0
  26. data/examples/ruby-versions/sharedFormulaTest-ruby/sheets/sheet1.rb +15 -0
  27. data/examples/ruby-versions/sharedFormulaTest-ruby/specs/sheet1_rspec.rb +44 -0
  28. data/examples/ruby-versions/sharedFormulaTest-ruby/spreadsheet.rb +9 -0
  29. data/examples/ruby-versions/table-test-ruby/sheets/sheet1.rb +17 -0
  30. data/examples/ruby-versions/table-test-ruby/sheets/sheet2.rb +5 -0
  31. data/examples/ruby-versions/table-test-ruby/sheets/sheet3.rb +5 -0
  32. data/examples/ruby-versions/table-test-ruby/specs/sheet1_rspec.rb +20 -0
  33. data/examples/ruby-versions/table-test-ruby/specs/sheet2_rspec.rb +8 -0
  34. data/examples/ruby-versions/table-test-ruby/specs/sheet3_rspec.rb +8 -0
  35. data/examples/ruby-versions/table-test-ruby/spreadsheet.rb +9 -0
  36. data/examples/sheets/array-formulas.xlsx +0 -0
  37. data/examples/sheets/complex-test.xlsx +0 -0
  38. data/examples/sheets/namedReferenceTest.xlsx +0 -0
  39. data/examples/sheets/pruning.xlsx +0 -0
  40. data/examples/sheets/sharedFormulaTest.xlsx +0 -0
  41. data/examples/sheets/table-test.xlsx +0 -0
  42. data/examples/sheets/~$array-formulas.xlsx +0 -0
  43. data/examples/unzipped-sheets/array-formulas/[Content_Types].xml +2 -0
  44. data/examples/unzipped-sheets/array-formulas/docProps/app.xml +2 -0
  45. data/examples/unzipped-sheets/array-formulas/docProps/core.xml +2 -0
  46. data/examples/unzipped-sheets/array-formulas/docProps/thumbnail.jpeg +0 -0
  47. data/examples/unzipped-sheets/array-formulas/xl/_rels/workbook.xml.rels +2 -0
  48. data/examples/unzipped-sheets/array-formulas/xl/calcChain.xml +2 -0
  49. data/examples/unzipped-sheets/array-formulas/xl/sharedStrings.xml +2 -0
  50. data/examples/unzipped-sheets/array-formulas/xl/styles.xml +2 -0
  51. data/examples/unzipped-sheets/array-formulas/xl/theme/theme1.xml +2 -0
  52. data/examples/unzipped-sheets/array-formulas/xl/workbook.xml +2 -0
  53. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet1.xml +2 -0
  54. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet2.xml +2 -0
  55. data/examples/unzipped-sheets/complex-test/[Content_Types].xml +2 -0
  56. data/examples/unzipped-sheets/complex-test/docProps/app.xml +2 -0
  57. data/examples/unzipped-sheets/complex-test/docProps/core.xml +2 -0
  58. data/examples/unzipped-sheets/complex-test/xl/_rels/workbook.xml.rels +2 -0
  59. data/examples/unzipped-sheets/complex-test/xl/calcChain.xml +2 -0
  60. data/examples/unzipped-sheets/complex-test/xl/charts/chart1.xml +2 -0
  61. data/examples/unzipped-sheets/complex-test/xl/charts/chart2.xml +2 -0
  62. data/examples/unzipped-sheets/complex-test/xl/comments1.xml +5 -0
  63. data/examples/unzipped-sheets/complex-test/xl/comments2.xml +5 -0
  64. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing1.xml.rels +2 -0
  65. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing2.xml.rels +2 -0
  66. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing1.xml +2 -0
  67. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing2.xml +2 -0
  68. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing1.vml +46 -0
  69. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing2.vml +46 -0
  70. data/examples/unzipped-sheets/complex-test/xl/sharedStrings.xml +2 -0
  71. data/examples/unzipped-sheets/complex-test/xl/styles.xml +2 -0
  72. data/examples/unzipped-sheets/complex-test/xl/theme/theme1.xml +2 -0
  73. data/examples/unzipped-sheets/complex-test/xl/workbook.xml +2 -0
  74. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  75. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet2.xml.rels +2 -0
  76. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet1.xml +2 -0
  77. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet2.xml +2 -0
  78. data/examples/unzipped-sheets/namedReferenceTest/[Content_Types].xml +2 -0
  79. data/examples/unzipped-sheets/namedReferenceTest/docProps/app.xml +2 -0
  80. data/examples/unzipped-sheets/namedReferenceTest/docProps/core.xml +2 -0
  81. data/examples/unzipped-sheets/namedReferenceTest/docProps/thumbnail.jpeg +0 -0
  82. data/examples/unzipped-sheets/namedReferenceTest/xl/_rels/workbook.xml.rels +2 -0
  83. data/examples/unzipped-sheets/namedReferenceTest/xl/calcChain.xml +2 -0
  84. data/examples/unzipped-sheets/namedReferenceTest/xl/styles.xml +2 -0
  85. data/examples/unzipped-sheets/namedReferenceTest/xl/theme/theme1.xml +2 -0
  86. data/examples/unzipped-sheets/namedReferenceTest/xl/workbook.xml +2 -0
  87. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet1.xml +2 -0
  88. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet2.xml +2 -0
  89. data/examples/unzipped-sheets/pruning/[Content_Types].xml +2 -0
  90. data/examples/unzipped-sheets/pruning/docProps/app.xml +2 -0
  91. data/examples/unzipped-sheets/pruning/docProps/core.xml +2 -0
  92. data/examples/unzipped-sheets/pruning/docProps/thumbnail.jpeg +0 -0
  93. data/examples/unzipped-sheets/pruning/xl/_rels/workbook.xml.rels +2 -0
  94. data/examples/unzipped-sheets/pruning/xl/calcChain.xml +2 -0
  95. data/examples/unzipped-sheets/pruning/xl/sharedStrings.xml +2 -0
  96. data/examples/unzipped-sheets/pruning/xl/styles.xml +2 -0
  97. data/examples/unzipped-sheets/pruning/xl/theme/theme1.xml +2 -0
  98. data/examples/unzipped-sheets/pruning/xl/workbook.xml +2 -0
  99. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet1.xml +2 -0
  100. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet2.xml +2 -0
  101. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet3.xml +2 -0
  102. data/examples/unzipped-sheets/sharedFormulaTest/[Content_Types].xml +2 -0
  103. data/examples/unzipped-sheets/sharedFormulaTest/docProps/app.xml +2 -0
  104. data/examples/unzipped-sheets/sharedFormulaTest/docProps/core.xml +2 -0
  105. data/examples/unzipped-sheets/sharedFormulaTest/docProps/thumbnail.jpeg +0 -0
  106. data/examples/unzipped-sheets/sharedFormulaTest/xl/_rels/workbook.xml.rels +2 -0
  107. data/examples/unzipped-sheets/sharedFormulaTest/xl/calcChain.xml +2 -0
  108. data/examples/unzipped-sheets/sharedFormulaTest/xl/styles.xml +2 -0
  109. data/examples/unzipped-sheets/sharedFormulaTest/xl/theme/theme1.xml +2 -0
  110. data/examples/unzipped-sheets/sharedFormulaTest/xl/workbook.xml +2 -0
  111. data/examples/unzipped-sheets/sharedFormulaTest/xl/worksheets/sheet1.xml +2 -0
  112. data/examples/unzipped-sheets/table-test/[Content_Types].xml +2 -0
  113. data/examples/unzipped-sheets/table-test/docProps/app.xml +2 -0
  114. data/examples/unzipped-sheets/table-test/docProps/core.xml +2 -0
  115. data/examples/unzipped-sheets/table-test/xl/_rels/workbook.xml.rels +2 -0
  116. data/examples/unzipped-sheets/table-test/xl/calcChain.xml +2 -0
  117. data/examples/unzipped-sheets/table-test/xl/printerSettings/printerSettings1.bin +0 -0
  118. data/examples/unzipped-sheets/table-test/xl/sharedStrings.xml +2 -0
  119. data/examples/unzipped-sheets/table-test/xl/styles.xml +2 -0
  120. data/examples/unzipped-sheets/table-test/xl/tables/table1.xml +2 -0
  121. data/examples/unzipped-sheets/table-test/xl/theme/theme1.xml +2 -0
  122. data/examples/unzipped-sheets/table-test/xl/workbook.xml +2 -0
  123. data/examples/unzipped-sheets/table-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  124. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet1.xml +2 -0
  125. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet2.xml +2 -0
  126. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet3.xml +2 -0
  127. data/lib/cells/array/array_formula_builder.rb +58 -0
  128. data/lib/cells/array/array_formula_cell.rb +27 -0
  129. data/lib/cells/array/arraying_formula_cell.rb +67 -0
  130. data/lib/cells/array/single_cell_array_formula_builder.rb +9 -0
  131. data/lib/cells/array/single_cell_array_formula_cell.rb +11 -0
  132. data/lib/cells/cell.rb +98 -0
  133. data/lib/cells/cells.rb +9 -0
  134. data/lib/cells/formula/formula_cell.rb +18 -0
  135. data/lib/cells/formula/simple_formula_cell.rb +4 -0
  136. data/lib/cells/shared/shared_formula_builder.rb +15 -0
  137. data/lib/cells/shared/shared_formula_cell.rb +20 -0
  138. data/lib/cells/shared/sharing_formula_cell.rb +36 -0
  139. data/lib/cells/value/value_cell.rb +24 -0
  140. data/lib/excelfile/excelfile.rb +6 -0
  141. data/lib/excelfile/relationships.rb +24 -0
  142. data/lib/excelfile/shared_strings.rb +21 -0
  143. data/lib/excelfile/sheet_names.rb +6 -0
  144. data/lib/excelfile/table.rb +116 -0
  145. data/lib/excelfile/workbook.rb +108 -0
  146. data/lib/excelfile/worksheet.rb +122 -0
  147. data/lib/formulae/compile/formula_builder.rb +316 -0
  148. data/lib/formulae/formulae.rb +6 -0
  149. data/lib/formulae/parse/formula_peg.rb +213 -0
  150. data/lib/formulae/parse/formula_peg.txt +40 -0
  151. data/lib/formulae/run/excel_functions.rb +375 -0
  152. data/lib/formulae/run/excel_matrix.rb +114 -0
  153. data/lib/formulae/run/excel_range.rb +256 -0
  154. data/lib/formulae/run/reference.rb +79 -0
  155. data/lib/optimiser/dependency_builder.rb +86 -0
  156. data/lib/optimiser/optimiser.rb +3 -0
  157. data/lib/optimiser/shared_formula_dependency_builder.rb +43 -0
  158. data/lib/optimiser/workbook_pruner.rb +80 -0
  159. data/lib/rubyfromexcel.rb +105 -0
  160. data/lib/runtime/runtime_formula_builder.rb +32 -0
  161. data/spec/array_formula_builder_spec.rb +35 -0
  162. data/spec/array_formula_cell_spec.rb +17 -0
  163. data/spec/arraying_formula_cell_spec.rb +38 -0
  164. data/spec/dependency_builder_spec.rb +71 -0
  165. data/spec/excel_functions_spec.rb +381 -0
  166. data/spec/excel_matrix_spec.rb +92 -0
  167. data/spec/excel_range_spec.rb +161 -0
  168. data/spec/formula_builder_spec.rb +230 -0
  169. data/spec/formula_peg_spec.rb +165 -0
  170. data/spec/reference_spec.rb +72 -0
  171. data/spec/relationships_spec.rb +51 -0
  172. data/spec/runtime_formula_builder_spec.rb +55 -0
  173. data/spec/shared_formula_builder_spec.rb +29 -0
  174. data/spec/shared_formula_cell_spec.rb +23 -0
  175. data/spec/shared_formula_dependency_builder_spec.rb +48 -0
  176. data/spec/shared_strings_spec.rb +14 -0
  177. data/spec/sharing_formula_cell_spec.rb +79 -0
  178. data/spec/simple_formula_cell_spec.rb +78 -0
  179. data/spec/single_cell_array_formula_builder_spec.rb +19 -0
  180. data/spec/single_cell_array_formula_cell_spec.rb +25 -0
  181. data/spec/spec_helper.rb +2 -0
  182. data/spec/table_spec.rb +100 -0
  183. data/spec/value_cell_spec.rb +49 -0
  184. data/spec/workbook_pruner_spec.rb +27 -0
  185. data/spec/workbook_spec.rb +283 -0
  186. data/spec/worksheet_failiures_spec.rb +41 -0
  187. data/spec/worksheet_spec.rb +486 -0
  188. metadata +291 -0
@@ -0,0 +1,92 @@
1
+ require_relative 'spec_helper'
2
+
3
+ class TestSheet2
4
+
5
+ def variable_name
6
+ 'sheet1'
7
+ end
8
+
9
+ def a1; 1.0; end
10
+ def a2; 2.0; end
11
+ def a3; 3.0; end
12
+
13
+ end
14
+
15
+ describe ExcelMatrixCollection do
16
+
17
+ it "can be created with a series of values" do
18
+ emc = ExcelMatrixCollection.new("one",[[3]],[[:a,:b,:c],[1,2,3]],ExcelMatrix.new([:a,:b,:c]))
19
+ end
20
+
21
+ it "can enumerate the values in the collection, making sure that each matrix is the size of the largest" do
22
+ emc = ExcelMatrixCollection.new("one",[[3]],[[:a,:b,:c],[1,2,3]],ExcelMatrix.new([:a,:b,:c]))
23
+ emc.to_a.should == [["one",3,:a,:a],["one",3,:b,:b],["one",3,:c,:c],["one",3,1,:a],["one",3,2,:b],["one",3,3,:c]]
24
+ end
25
+
26
+ it "can map the values to a new excel matrix of he size of the largest argument" do
27
+ emc = ExcelMatrixCollection.new("one",[[3]],[[:a,:b,:c],[1,2,3]],ExcelMatrix.new([:a,:b,:c]))
28
+ emc.matrix_map do |a,b,c,d,e|
29
+ 1
30
+ end.values.should == [[1,1,1],[1,1,1]]
31
+ end
32
+
33
+ it "if the mapped ExcelMatrix is a single cell, then transforms into that" do
34
+ result = ExcelMatrixCollection.new([[3]]).matrix_map do |r1|
35
+ Area.new(TestSheet2.new,'a1','a3')
36
+ end
37
+ result.should be_kind_of(Area)
38
+ result.to_a.should == [1.0,2.0,3.0]
39
+ result.array_formula_offset(1,0).should == 2.0
40
+ result = ExcelMatrixCollection.new([[3]]).matrix_map do |r1|
41
+ r1 + 1
42
+ end
43
+ result.should == 4
44
+ end
45
+
46
+ end
47
+
48
+ describe ExcelMatrix do
49
+
50
+ it "can be created from a single value" do
51
+ em = ExcelMatrix.new(3)
52
+ em.rows.should == 1
53
+ em.columns.should == 1
54
+ em.values.should == [[3]]
55
+ end
56
+
57
+ it "can be created from an array of arrays" do
58
+ em = ExcelMatrix.new([[:a,:b,:c],[1,2,3]])
59
+ em.rows.should == 2
60
+ em.columns.should == 3
61
+ em.values.should == [[:a,:b,:c],[1,2,3]]
62
+ end
63
+
64
+ it "can be created from a single array" do
65
+ em = ExcelMatrix.new([:a,:b,:c])
66
+ em.rows.should == 1
67
+ em.columns.should == 3
68
+ em.values.should == [[:a,:b,:c]]
69
+ end
70
+
71
+ it "can have its number of rows expanded" do
72
+ em = ExcelMatrix.new([:a,:b,:c])
73
+ em.rows.should == 1
74
+ em.add_rows!(2)
75
+ em.rows.should == 3
76
+ em.values.should == [[:a,:b,:c],[:a,:b,:c],[:a,:b,:c]]
77
+ end
78
+
79
+ it "can have its number of columns expanded" do
80
+ em = ExcelMatrix.new([[:a],[1]])
81
+ em.columns.should == 1
82
+ em.add_columns!(2)
83
+ em.columns.should == 3
84
+ em.values.should == [[:a,:a,:a],[1,1,1]]
85
+ end
86
+
87
+ it "responds to #array_formula_offset(row_index,column_index) with origin 0,0" do
88
+ em = ExcelMatrix.new([[1,2,3],[4,5,6]])
89
+ em.array_formula_offset(0,0).should == 1
90
+ em.array_formula_offset(1,1).should == 5
91
+ end
92
+ end
@@ -0,0 +1,161 @@
1
+ require_relative 'spec_helper'
2
+
3
+ class TestSheet
4
+ include RubyFromExcel::ExcelFunctions
5
+
6
+ {
7
+ :a => ['a1', 'a2', 'a3', nil , 'a5'],
8
+ :b => ['b1', nil, nil, 'b4', 'b5'],
9
+ :c => ['c1', 'c2', 'c3', 'c4', 'c5']
10
+ }.each do |column,row_values|
11
+ row_values.each_with_index do |value,row|
12
+ next unless value
13
+ class_eval "def #{column}#{row+1}; '#{value}'; end"
14
+ end
15
+ end
16
+
17
+ def to_s
18
+ 'sheet1'
19
+ end
20
+
21
+ end
22
+
23
+ describe Area do
24
+ it "should return the values in an area" do
25
+ Area.new(TestSheet.new,'a1','a3').to_a.should == ['a1','a2','a3']
26
+ Area.new(TestSheet.new,'a1','b1').to_a.should == ['a1','b1']
27
+ end
28
+
29
+ it "should return references in its area" do
30
+ Area.new(TestSheet.new,'a1','a3').to_reference_enum.to_a.should == ['a1','a2','a3']
31
+ Area.new(TestSheet.new,'r9','r11').to_reference_enum.to_a.should == ['r9','r10','r11']
32
+ Area.new(TestSheet.new,'zx10','zz10').to_reference_enum.to_a.should == ['zx10','zy10','zz10']
33
+ end
34
+
35
+ it "should respond to array_formula_index(row_index,column_index) by returning the reference at those coordinates" do
36
+ Area.new(TestSheet.new,'a1','c5').array_formula_index(0,0).to_ruby.should == 'a1'
37
+ Area.new(TestSheet.new,'a1','c5').array_formula_index(1,1).to_ruby.should == 'b2'
38
+ Area.new(TestSheet.new,'a1','c5').array_formula_index(10,10).to_ruby.should == :ref
39
+ end
40
+
41
+ it "should respond to array_formula_offset(row_index,column_index) by returning the value at those coordinates (origin is 0,0)" do
42
+ Area.new(TestSheet.new,'a1','c5').array_formula_offset(0,0).should == 'a1'
43
+ Area.new(TestSheet.new,'a1','c5').array_formula_offset(1,1).should == 0.0
44
+ Area.new(TestSheet.new,'a1','c5').array_formula_offset(10,10).should == :ref
45
+ end
46
+
47
+ it "if the area is a single column, then it should ignore the column index when responding to array_formula_index(row_index,column_index)" do
48
+ Area.new(TestSheet.new,'a1','a5').array_formula_index(0,1).to_ruby.should == 'a1'
49
+ Area.new(TestSheet.new,'a1','a5').array_formula_index(1,1).to_ruby.should == 'a2'
50
+ Area.new(TestSheet.new,'a1','a5').array_formula_index(6,1).to_ruby.should == :ref
51
+ end
52
+
53
+ it "if the area is a single row, then it should ignore the row index when responding to array_formula_index(row_index,column_index)" do
54
+ Area.new(TestSheet.new,'a1','c1').array_formula_index(1,0).to_ruby.should == 'a1'
55
+ Area.new(TestSheet.new,'a1','c1').array_formula_index(1,1).to_ruby.should == 'b1'
56
+ Area.new(TestSheet.new,'a1','c1').array_formula_index(6,4).to_ruby.should == :ref
57
+ end
58
+
59
+ it "#row(index) returns a subset of the area for just that row (index=0 is the top row, index=-1 is the last row)" do
60
+ Area.new(TestSheet.new,'a1','c5').row(0).start_cell.to_s.should == 'a1'
61
+ Area.new(TestSheet.new,'a1','c5').row(0).end_cell.to_s.should == 'c1'
62
+ Area.new(TestSheet.new,'a1','c5').row(-1).start_cell.to_s.should == 'a5'
63
+ Area.new(TestSheet.new,'a1','c5').row(-1).end_cell.to_s.should == 'c5'
64
+ end
65
+
66
+ it "#rows(start_index,end_index) returns a subset of the area for those rows (index=0 is the top row, index=-1 is the last row)" do
67
+ Area.new(TestSheet.new,'a1','c5').rows(0,-1).start_cell.to_s.should == 'a1'
68
+ Area.new(TestSheet.new,'a1','c5').rows(0,-1).end_cell.to_s.should == 'c5'
69
+ Area.new(TestSheet.new,'a1','c5').rows(1,-2).start_cell.to_s.should == 'a2'
70
+ Area.new(TestSheet.new,'a1','c5').rows(1,-2).end_cell.to_s.should == 'c4'
71
+ end
72
+
73
+ it "#column(index) returns a subset of the area for just that column (index=0 is the first column)" do
74
+ Area.new(TestSheet.new,'a1','c5').column(0).start_cell.to_s.should == 'a1'
75
+ Area.new(TestSheet.new,'a1','c5').column(0).end_cell.to_s.should == 'a5'
76
+ Area.new(TestSheet.new,'a1','c5').column(2).start_cell.to_s.should == 'c1'
77
+ Area.new(TestSheet.new,'a1','c5').column(2).end_cell.to_s.should == 'c5'
78
+ end
79
+
80
+ it "#to_s returns the area in the form of the excel function used to create it" do
81
+ Area.new(TestSheet.new,'a1','c5').to_s.should == "sheet1.a('a1','c5')"
82
+ end
83
+
84
+ it "#to_excel_matrix returns the area in the form of a matrix, with methods to allow excel array formula manipulations" do
85
+ em = Area.new(TestSheet.new,'a1','c5').to_excel_matrix
86
+ em.rows.should == 5
87
+ em.columns.should == 3
88
+ em.values.should == [['a1','b1','c1'],['a2',0.0,'c2'],['a3',0.0,'c3'],[0.0,'b4','c4'],['a5','b5','c5']]
89
+ end
90
+
91
+ end
92
+
93
+ describe Area, "when area refers to just a single cell" do
94
+
95
+ it "should duck type as if it were just that cell for use in arithmetic, string joins etc" do
96
+ (Area.new(TestSheet.new,'a1','a1') + "-cell").should == "a1-cell"
97
+ (1 + Area.new(TestSheet.new,'b3','b3') + 10).should == 11
98
+ end
99
+
100
+ end
101
+
102
+ describe Columns do
103
+ it "should return all the defined values in a single column" do
104
+ Columns.new(TestSheet.new,'c','c').each.to_a.should == ['c1', 'c2', 'c3', 'c4', 'c5']
105
+ end
106
+
107
+ it "should cope with undefined values where they are in the middle of the column" do
108
+ Columns.new(TestSheet.new,'a','a').each.to_a.should == ['a1','a2','a3',0.0,'a5']
109
+ end
110
+
111
+ it "should return all the defined values in several columns as a single array" do
112
+ Columns.new(TestSheet.new,'a','c').each.to_a.should == ['a1','a2','a3',0.0,'a5','b1',0.0,0.0,'b4','b5','c1', 'c2', 'c3', 'c4', 'c5']
113
+ end
114
+
115
+ it "should cope with references to columns that aren't defined at all" do
116
+ Columns.new(TestSheet.new,'aa','zz').each.to_a.should == []
117
+ end
118
+
119
+ it "should respond to array_formula_index(row_index,column_index) by returning the reference for those coordinates" do
120
+ Columns.new(TestSheet.new,'a','c').array_formula_index(0,0).should == 'a1'
121
+ Columns.new(TestSheet.new,'a','c').array_formula_index(1,1).should == 'b2'
122
+ Columns.new(TestSheet.new,'a','c').array_formula_index(10,10).should == :na
123
+ end
124
+
125
+ it "if the area is a single column, then it should ignore the column index when responding to array_formula_index(row_index,column_index)" do
126
+ Columns.new(TestSheet.new,'c','c').array_formula_index(0,1).should == 'c1'
127
+ Columns.new(TestSheet.new,'c','c').array_formula_index(1,1).should == 'c2'
128
+ Columns.new(TestSheet.new,'c','c').array_formula_index(6,6).should == 'c7'
129
+ end
130
+
131
+ end
132
+
133
+ describe Rows do
134
+ it "should return all the defined values in a single row" do
135
+ Rows.new(TestSheet.new,1,1).each.to_a.should == ['a1','b1','c1']
136
+ end
137
+
138
+ it "should cope with undefined values where they are in the middle of the row" do
139
+ Rows.new(TestSheet.new,2,2).each.to_a.should == ['a2',0.0,'c2']
140
+ end
141
+
142
+ it "should return all the defined values in several columns as a single array" do
143
+ Rows.new(TestSheet.new,1,4).each.to_a.should == ['a1','b1','c1','a2',0.0,'c2','a3',0.0,'c3',0.0,'b4','c4']
144
+ end
145
+
146
+ it "should cope with references to rows that aren't defined at all" do
147
+ Rows.new(TestSheet.new,100,105).each.to_a.should == []
148
+ end
149
+
150
+ it "should respond to array_formula_index(row_index,column_index) by returning the reference at those coordinates" do
151
+ Rows.new(TestSheet.new,1,4).array_formula_index(0,0).should == 'a1'
152
+ Rows.new(TestSheet.new,1,4).array_formula_index(1,1).should == 'b2'
153
+ Rows.new(TestSheet.new,1,4).array_formula_index(10,10).should == :na
154
+ end
155
+
156
+ it "if the area is a single row, then it should ignore the row index when responding to array_formula_index(row_index,column_index)" do
157
+ Rows.new(TestSheet.new,1,1).array_formula_index(1,0).should == 'a1'
158
+ Rows.new(TestSheet.new,1,1).array_formula_index(1,1).should == 'b1'
159
+ Rows.new(TestSheet.new,1,1).array_formula_index(6,6).should == 'g1'
160
+ end
161
+ end
@@ -0,0 +1,230 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe FormulaBuilder do
4
+
5
+ before(:each) do
6
+ @builder = FormulaBuilder.new
7
+ SheetNames.instance['asheetname'] = 'sheet1'
8
+ SheetNames.instance['a long sheet name with spaces'] = 'sheet2'
9
+ end
10
+
11
+ def ruby_for(formula)
12
+ ast = Formula.parse(formula)
13
+ ast.visit(@builder)
14
+ end
15
+
16
+ it "Should convert simple references to ruby method calls" do
17
+ ruby_for("AA1").should == "aa1"
18
+ ruby_for("$AA$100").should == "aa100"
19
+ ruby_for("A$1").should == "a1"
20
+ ruby_for("$A1").should == "a1"
21
+ ruby_for("I9+J9+K9+P9+Q9").should == "i9+j9+k9+p9+q9"
22
+ end
23
+
24
+ it "Should convert simple arithmetic, converting numbers to floats" do
25
+ ruby_for("(1+3)*(12+13/2.0E-12)").should == "(1.0+3.0)*(12.0+13.0/2.0e-12)"
26
+ ruby_for("((1+3)*(12+13/2.0))").should == "((1.0+3.0)*(12.0+13.0/2.0))"
27
+ end
28
+
29
+ it "Should convert percentages appropriately to their float equivalents" do
30
+ ruby_for("1+3%").should == "1.0+0.03"
31
+ ruby_for("1+103.12%").should == "1.0+1.0312"
32
+ end
33
+
34
+ it "Should convert powers to their ruby equivalent" do
35
+ ruby_for("1^12").should == "1.0**12.0"
36
+ end
37
+
38
+ it "Should join strings together, adding to_s where appropriate" do
39
+ ruby_for('"Hello"&"world"').should == '"Hello"+"world"'
40
+ ruby_for('"Hello "&A1').should == '"Hello "+a1.to_s'
41
+ ruby_for('AA1&"GW"').should == 'aa1.to_s+"GW"'
42
+ ruby_for('"GW"&ISERR($AA$1)').should == '"GW"+iserr(aa1).to_s'
43
+ end
44
+
45
+ it "Should convert area references to a(start,end) functions" do
46
+ ruby_for('$A$1:BZ$2000').should == "a('a1','bz2000')"
47
+ end
48
+
49
+ it "Should convert column references to c(start,end) functions" do
50
+ ruby_for('A:ZZ').should == "c('a','zz')"
51
+ end
52
+
53
+ it "should convert row references to r(start,end) functions" do
54
+ ruby_for('1:1000').should == 'r(1,1000)'
55
+ end
56
+
57
+ it "should leave strings as they are, even if they look like formulas" do
58
+ ruby_for('"A1+3*2"&$A3').should == '"A1+3*2"+a3.to_s'
59
+ end
60
+
61
+ it "should properly escape strings where necessary" do
62
+ ruby_for(%q{"String with 'quotes' in it"}).should == '"String with \'quotes\' in it"'
63
+ end
64
+
65
+ it "should convert sheet names to ruby methods" do
66
+ ruby_for("asheetname!A3:B20").should == "sheet1.a('a3','b20')"
67
+ ruby_for("'a long sheet name with spaces'!A3:B20").should == "sheet2.a('a3','b20')"
68
+ end
69
+
70
+ it "should convert named references to ruby methods and inline their values" do
71
+ worksheet = mock(:worksheet)
72
+ workbook = mock(:workbook)
73
+ @builder.formula_cell = mock(:cell,:worksheet => worksheet)
74
+ worksheet.should_receive(:named_references).and_return({"one_and2"=>'sheet1.a(\'a1\',\'f10\')'})
75
+ ruby_for("SUM(OneAnd2)").should == "sum(sheet1.a('a1','f10'))"
76
+ worksheet.should_receive(:named_references).and_return({})
77
+ worksheet.should_receive(:workbook).and_return(workbook)
78
+ workbook.should_receive(:named_references).and_return({"reference_one" => "sheet10.a1"})
79
+ ruby_for("ReferenceOne").should == "sheet10.a1"
80
+ worksheet.should_receive(:named_references).and_return({"one_and2"=>'sheet1.a(\'a1\',\'f10\')'})
81
+ worksheet.should_receive(:workbook).and_return(workbook)
82
+ workbook.should_receive(:named_references).and_return({"reference_one" => "sheet10.a1"})
83
+ ruby_for("Reference.2").should == ":name"
84
+ worksheet.should_receive(:named_references).and_return({"one_and2"=>'sheet1.a(\'a1\',\'f10\')'})
85
+ worksheet.should_receive(:workbook).and_return(workbook)
86
+ workbook.should_receive(:named_references).and_return({"reference_one" => "sheet10.a1","ef_natural_gas_n2o"=> "sheet10.a1"})
87
+ ruby_for("-($AG70+$X70)*EF.NaturalGas.N2O").should == "-(ag70+x70)*sheet10.a1"
88
+ end
89
+
90
+ it "should convert table names to references" do
91
+ Table.new(mock(:worksheet,:to_s => 'sheet1'),'Vectors','a1:c41',['ColA','Description','ColC'],1)
92
+ ruby_for("Vectors[Description]").should == "sheet1.a('b2','b40')"
93
+ ruby_for("Vectors[#all]").should == "sheet1.a('a1','c41')"
94
+ ruby_for("Vectors[#totals]").should == "sheet1.a('a41','c41')"
95
+ ruby_for("Vectors[[#totals],[Description]]").should == "sheet1.b41"
96
+ @builder.formula_cell = mock(:cell,:reference => Reference.new('f30'))
97
+ ruby_for("Vectors[[#This Row],[Description]]").should == "sheet1.b30"
98
+ end
99
+
100
+ it "should convert unkown table names to :ref" do
101
+ ruby_for("Unknown[Not likely]").should == ":ref"
102
+ end
103
+
104
+ it "should convert unqualified table names to references" do
105
+ sheet = mock(:worksheet,:to_s => 'sheet1')
106
+ Table.new(sheet,'Vectors','a1:c41',['ColA','Description','ColC'],1)
107
+ @builder.formula_cell = mock(:cell,:reference => Reference.new('c30',sheet), :worksheet => sheet)
108
+ ruby_for("[Description]").should == "sheet1.b30"
109
+ ruby_for("[#all]").should == "sheet1.a('a1','c41')"
110
+ ruby_for("[#headers]").should == "sheet1.c1"
111
+ ruby_for("[#totals]").should == "sheet1.c41"
112
+ ruby_for("[[#totals],[Description]]").should == "sheet1.b41"
113
+ ruby_for("[[#This Row],[Description]]").should == "sheet1.b30"
114
+ end
115
+
116
+ it "should convert booleans to their ruby equivalents" do
117
+ ruby_for("TRUE*FALSE").should == "true*false"
118
+ end
119
+
120
+ it "should convert the not function to !()" do
121
+ ruby_for("NOT(TRUE)").should == "!(true)"
122
+ end
123
+
124
+ it "should throw an exception if trying to convert an unknown function" do
125
+ lambda { ruby_for("NEWEXCELFUNCTION(TRUE,FALSE)") }.should raise_error(ExcelFunctionNotImplementedError)
126
+ end
127
+
128
+ it "should convert clashing excel function names to excel_name variants" do
129
+ ruby_for("IF(TRUE,FALSE,TRUE)").should == "excel_if(true,false,true)"
130
+ ruby_for("AND(TRUE,FALSE)").should == "excel_and(true,false)"
131
+ ruby_for("OR(TRUE,FALSE)").should == "excel_or(true,false)"
132
+ end
133
+
134
+ it "should add the cell reference as a second argument to indirect() functions, setting workbook.indirects_used to true" do
135
+ worksheet = mock(:worksheet)
136
+ workbook = mock(:workbook)
137
+ worksheet.should_receive(:workbook).and_return(workbook)
138
+ cell = mock(:cell,:value => 'ASD',:can_be_replaced_with_value? => false)
139
+ worksheet.should_receive(:cell).with('a1').and_return(cell)
140
+ workbook.should_receive(:indirects_used=).with(true)
141
+ @builder.formula_cell = mock(:cell,:reference => Reference.new('f30'),:worksheet => worksheet)
142
+ ruby_for('INDIRECT("ONE"&A1)').should == 'indirect("ONE"+a1.to_s,\'f30\')'
143
+ end
144
+
145
+ it "should attempt to interpret indirect functions where that is appropriate" do
146
+ worksheet = mock(:worksheet, :to_s => 'sheet1')
147
+ @builder.formula_cell = mock(:cell,:reference => Reference.new('f30',worksheet),:worksheet => worksheet)
148
+ ruby_for('INDIRECT("A1")').should == "a1"
149
+ ruby_for('INDIRECT("A"&"1")').should == "a1"
150
+ ruby_for('INDIRECT("A"&1)').should == "a1"
151
+
152
+ SheetNames.instance['sheet100'] = 'sheet100'
153
+ cell = mock(:cell,:value_for_including => 'sheet100',:can_be_replaced_with_value? => true)
154
+ worksheet.should_receive(:cell).with('a1').and_return(cell)
155
+ ruby_for('INDIRECT(A1&"!A1")').should == "sheet100.a1"
156
+
157
+ SheetNames.instance['sheet100'] = 'sheet100'
158
+ workbook = mock(:workbook)
159
+ worksheet.should_receive(:workbook).and_return(workbook)
160
+ workbook.should_receive(:worksheets).and_return({'sheet100' => worksheet})
161
+ worksheet.should_receive(:cell).with('a1').and_return(cell)
162
+ ruby_for('INDIRECT(sheet100!A1&"!A1")').should == "sheet100.a1"
163
+
164
+ worksheet.should_receive(:named_references).and_return({"this_year" => 'sheet1.a1'})
165
+ worksheet.should_receive(:workbook).and_return(workbook)
166
+ workbook.should_receive(:worksheets).and_return({'sheet1' => worksheet})
167
+ worksheet.should_receive(:cell).with('a1').and_return(nil)
168
+ ruby_for('INDIRECT(this.Year & "!A1")').should == ":name"
169
+
170
+ Table.new(worksheet,'Vectors','a1:c41',['ColA','Description','ColC'],1)
171
+ worksheet.should_receive(:workbook).and_return(workbook)
172
+ workbook.should_receive(:worksheets).and_return({'sheet1' => worksheet})
173
+ worksheet.should_receive(:cell).with('b30').and_return(cell)
174
+ ruby_for('INDIRECT(Vectors[[#This Row],[Description]] & "!A1")').should == "sheet100.a1"
175
+
176
+ Table.new(worksheet,'Vectors','a1:f41',['ColA','Description','ColC','ColD','ColE','ColF'],1)
177
+ worksheet.should_receive(:workbook).and_return(workbook)
178
+ workbook.should_receive(:worksheets).and_return({'sheet1' => worksheet})
179
+ worksheet.should_receive(:cell).with('f1').and_return(cell)
180
+ ruby_for('INDIRECT([#Headers]& "!A1")').should == "sheet100.a1"
181
+
182
+ worksheet.should_receive(:cell).with('c120').and_return(cell)
183
+ ruby_for('INDIRECT($C120&".Outputs[Vector]")').should == ":ref"
184
+ end
185
+
186
+
187
+ it "should convert comparators into single expressions so that they work in ifs" do
188
+ ruby_for('IF(A1=3,"A","B")').should == 'excel_if(a1==3.0,"A","B")'
189
+ end
190
+
191
+ it "should convert complex formulas" do
192
+ # SheetNames.instance['DUKES 09 (2.5)'] = 'sheet100'
193
+ # SheetNames.instance['DUKES 09 (1.2)'] = 'sheet101'
194
+ # ruby_for("(-'DUKES 09 (2.5)'!$B$25*1000000*Constants.GCV.Coke)+(-'DUKES 09 (2.5)'!$C$25*1000000*Constants.GCV.CokeBreeze)+('DUKES 09 (1.2)'!$B$22*Unit.ktoe)").should == "(-sheet100.b25*1000000.0*constants_gcv_coke)+(-sheet100.c25*1000000.0*constants_gcv_coke_breeze)+(sheet101.b22*unit_ktoe)"
195
+ # complex_formula = %q{-(INDEX(INDIRECT(BI$19&"!Year.Matrix"),MATCH("Subtotal.Supply",INDIRECT(BI$19&"!Year.Modules"),0),MATCH("V.04",INDIRECT(BI$19&"!Year.Vectors"),0))+INDEX(INDIRECT(BI$19&"!Year.Matrix"),MATCH("Subtotal.Consumption",INDIRECT(BI$19&"!Year.Modules"),0),MATCH("V.04",INDIRECT(BI$19&"!Year.Vectors"),0)))}
196
+ # # ruby_for(complex_formula).should == "-(index(indirect(bi19.to_s+\"!Year.Matrix\",''),match(\"Subtotal.Supply\",indirect(bi19.to_s+\"!Year.Modules\",''),0.0),match(\"V.04\",indirect(bi19.to_s+\"!Year.Vectors\",''),0.0))+index(indirect(bi19.to_s+\"!Year.Matrix\",''),match(\"Subtotal.Consumption\",indirect(bi19.to_s+\"!Year.Modules\",''),0.0),match(\"V.04\",indirect(bi19.to_s+\"!Year.Vectors\",''),0.0)))"
197
+ # ruby_for("MAX(MIN(F121, -F22),0)").should == "max(min(f121,-f22),0.0)"
198
+ end
199
+
200
+
201
+ it "should replace MATCH() with its answer where it depends only on cells that can be replaced with their values" do
202
+ worksheet = mock(:worksheet, :to_s => 'sheet1')
203
+ a1 = mock(:cell,:reference => Reference.new('a1',worksheet),:worksheet => worksheet,:value_for_including => 'A', :can_be_replaced_with_value? => true)
204
+ a2 = mock(:cell,:reference => Reference.new('a2',worksheet),:worksheet => worksheet,:value_for_including => 'A', :can_be_replaced_with_value? => true)
205
+ a3 = mock(:cell,:reference => Reference.new('a3',worksheet),:worksheet => worksheet,:value_for_including => 'B', :can_be_replaced_with_value? => true)
206
+ worksheet.should_receive(:cell).with('a1').and_return(a1)
207
+ worksheet.should_receive(:cell).with('a2').and_return(a2)
208
+ worksheet.should_receive(:cell).with('a3').and_return(a3)
209
+ @builder.formula_cell = mock(:cell,:reference => Reference.new('f30',worksheet),:worksheet => worksheet)
210
+ ruby_for('MATCH("A",A1:A3)').should == "2.0"
211
+ ruby_for('MATCH("X",A1:A3,FALSE)').should == "na"
212
+ end
213
+
214
+ it "should replace INDEX() with a cell reference where it depends only on cells that can be replaced with their values" do
215
+ worksheet = mock(:worksheet, :to_s => 'sheet1')
216
+ @builder.formula_cell = mock(:cell,:reference => Reference.new('f30',worksheet),:worksheet => worksheet)
217
+ ruby_for('INDEX(A1:A3,3.0)').should == "sheet1.a3"
218
+ ruby_for('INDEX(A1:A3,2.0,1.0)').should == "sheet1.a2"
219
+ end
220
+
221
+ it "should put multiple items as single arguments in a function" do
222
+ ruby_for("IF(A23>=(1.0+B38),1.0,2.0)").should == "excel_if(a23>=(1.0+b38),1.0,2.0)"
223
+ ruby_for("MAX(F60+(G$59-F$59)*G38,0)").should == "max(f60+(g59-f59)*g38,0.0)"
224
+ end
225
+
226
+ it "should convert formulas with null arguments, replacing the null with 0.0" do
227
+ spaced4 = "SUMIFS(INDEX($G$62:$J$73, , MATCH($E$11, $G$61:$J$61, 0)), $C$62:$C$73, $C195, $D$62:$D$73, $D195)"
228
+ ruby_for(spaced4).should == "sumifs(index(a('g62','j73'),0.0,match(e11,a('g61','j61'),0.0)),a('c62','c73'),c195,a('d62','d73'),d195)"
229
+ end
230
+ end
@@ -0,0 +1,165 @@
1
+ require_relative 'spec_helper'
2
+
3
+ require 'textpeg2rubypeg'
4
+ text_peg = IO.readlines(File.join(File.dirname(__FILE__),'..','lib','formulae','parse','formula_peg.txt')).join
5
+ ast = TextPeg.parse(text_peg)
6
+ # puts ast.to_ast
7
+ # exit
8
+ builder = TextPeg2RubyPeg.new
9
+ ruby = ast.visit(builder)
10
+ Kernel.eval(ruby)
11
+
12
+ describe Formula do
13
+
14
+ def check(text)
15
+ puts
16
+ e = Formula.new
17
+ e.parse(text)
18
+ e.pretty_print_cache(true)
19
+ puts
20
+ end
21
+
22
+ it "returns formulas" do
23
+ Formula.parse('1+1').to_ast.first.should == :formula
24
+ end
25
+
26
+ it "returns cells" do
27
+ Formula.parse('$A$1').to_ast.should == [:formula,[:cell,'$A$1']]
28
+ Formula.parse('A1').to_ast.should == [:formula,[:cell,'A1']]
29
+ Formula.parse('$A1').to_ast.should == [:formula,[:cell,'$A1']]
30
+ Formula.parse('A$1').to_ast.should == [:formula,[:cell,'A$1']]
31
+ Formula.parse('AAA1123').to_ast.should == [:formula,[:cell,'AAA1123']]
32
+ Formula.parse("IF(a23>=(1.0+b38),1.0,2.0)") == [:formula, [:function, "IF", [:comparison, [:cell, "a23"], [:comparator, ">="], [:brackets, [:arithmetic, [:number, "1.0"], [:operator, "+"], [:cell, "b38"]]]], [:number, "1.0"], [:number, "2.0"]]]
33
+ end
34
+
35
+ it "returns areas" do
36
+ Formula.parse('$A$1:$Z$1').to_ast.should == [:formula,[:area,'$A$1','$Z$1']]
37
+ Formula.parse('A1:$Z$1').to_ast.should == [:formula,[:area,'A1','$Z$1']]
38
+ end
39
+
40
+ it "returns row ranges" do
41
+ Formula.parse('$1:$1000').to_ast.should == [:formula,[:row_range,'$1','$1000']]
42
+ Formula.parse('1000:1').to_ast.should == [:formula,[:row_range,'1000','1']]
43
+ end
44
+
45
+ it "returns column ranges" do
46
+ Formula.parse('$C:$AZ').to_ast.should == [:formula,[:column_range,'$C','$AZ']]
47
+ Formula.parse('C:AZ').to_ast.should == [:formula,[:column_range,'C','AZ']]
48
+ end
49
+
50
+ it "returns references to other sheets" do
51
+ Formula.parse('sheet1!$A$1').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:cell,'$A$1']]]
52
+ Formula.parse('sheet1!$A$1:$Z$1').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:area,'$A$1','$Z$1']]]
53
+ Formula.parse('sheet1!$1:$1000').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:row_range,'$1','$1000']]]
54
+ Formula.parse('sheet1!$C:$AZ').to_ast.should == [:formula,[:sheet_reference,'sheet1',[:column_range,'$C','$AZ']]]
55
+ end
56
+
57
+ it "returns references to other sheets with extended names" do
58
+ Formula.parse("'sheet 1'!$A$1").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:cell,'$A$1']]]
59
+ Formula.parse("'sheet 1'!$A$1:$Z$1").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:area,'$A$1','$Z$1']]]
60
+ Formula.parse("'sheet 1'!$1:$1000").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:row_range,'$1','$1000']]]
61
+ Formula.parse("'sheet 1'!$C:$AZ").to_ast.should == [:formula,[:quoted_sheet_reference,'sheet 1',[:column_range,'$C','$AZ']]]
62
+ Formula.parse("'2007.0'!Year.Matrix").to_ast.should == [:formula, [:quoted_sheet_reference, "2007.0", [:named_reference, "Year.Matrix"]]]
63
+ end
64
+
65
+ it "returns numbers" do
66
+ Formula.parse("1").to_ast.should == [:formula,[:number,'1']]
67
+ Formula.parse("103.287").to_ast.should == [:formula,[:number,'103.287']]
68
+ Formula.parse("-1.0E-27").to_ast.should == [:formula,[:number,'-1.0E-27']]
69
+ end
70
+
71
+ it "returns percentages" do
72
+ Formula.parse("1%").to_ast.should == [:formula,[:percentage,'1']]
73
+ Formula.parse("103.287%").to_ast.should == [:formula,[:percentage,'103.287']]
74
+ Formula.parse("-1.0%").to_ast.should == [:formula,[:percentage,'-1.0']]
75
+ end
76
+
77
+ it "returns strings" do
78
+ Formula.parse('"A handy string"').to_ast.should == [:formula,[:string,"A handy string"]]
79
+ Formula.parse('"$A$1"').to_ast.should == [:formula,[:string,"$A$1"]]
80
+ end
81
+
82
+ it "returns string joins" do
83
+ Formula.parse('"A handy string"&$A$1').to_ast.should == [:formula,[:string_join,[:string,"A handy string"],[:cell,'$A$1']]]
84
+ Formula.parse('$A$1&"A handy string"').to_ast.should == [:formula,[:string_join,[:cell,'$A$1'],[:string,"A handy string"]]]
85
+ Formula.parse('$A$1&"A handy string"&$A$1').to_ast.should == [:formula,[:string_join,[:cell,'$A$1'],[:string,"A handy string"],[:cell,'$A$1'],]]
86
+ Formula.parse('$A$1&$A$1&$A$1').to_ast.should == [:formula,[:string_join,[:cell,'$A$1'],[:cell,'$A$1'],[:cell,'$A$1'],]]
87
+ Formula.parse('"GW"&ISERR($AA$1)').to_ast.should == [:formula,[:string_join,[:string,'GW'],[:function,'ISERR',[:cell,'$AA$1']]]]
88
+ end
89
+
90
+ it "returns numeric operations" do
91
+ Formula.parse('$A$1+$A$2+1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"+"],[:cell,'$A$2'],[:operator,"+"],[:number,'1']]]
92
+ Formula.parse('$A$1-$A$2-1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"-"],[:cell,'$A$2'],[:operator,"-"],[:number,'1']]]
93
+ Formula.parse('$A$1*$A$2*1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"*"],[:cell,'$A$2'],[:operator,"*"],[:number,'1']]]
94
+ Formula.parse('$A$1/$A$2/1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"/"],[:cell,'$A$2'],[:operator,"/"],[:number,'1']] ]
95
+ Formula.parse('$A$1^$A$2^1').to_ast.should == [:formula,[:arithmetic,[:cell,'$A$1'],[:operator,"^"],[:cell,'$A$2'],[:operator,"^"],[:number,'1']]]
96
+ end
97
+
98
+ it "returns expressions in brackets" do
99
+ Formula.parse('($A$1+$A$2)').to_ast.should == [:formula,[:brackets,[:arithmetic,[:cell,'$A$1'],[:operator,"+"],[:cell,'$A$2']]]]
100
+ Formula.parse('($A$1+$A$2)+2').to_ast.should == [:formula, [:arithmetic, [:brackets, [:arithmetic, [:cell,'$A$1'], [:operator,"+"], [:cell,'$A$2']]], [:operator,"+"], [:number,'2']]]
101
+ Formula.parse('($A$1+$A$2)+(2+(1*2))').to_ast.should == [:formula, [:arithmetic, [:brackets, [:arithmetic, [:cell,'$A$1'], [:operator,"+"], [:cell,'$A$2']]], [:operator,"+"], [:brackets, [:arithmetic, [:number,'2'], [:operator,'+'] ,[:brackets, [:arithmetic, [:number,'1'], [:operator,'*'], [:number,'2']]]]]]]
102
+ end
103
+
104
+ it "returns comparisons" do
105
+ Formula.parse('$A$1>$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,">"],[:cell,'$A$2']]]
106
+ Formula.parse('$A$1<$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"<"],[:cell,'$A$2']]]
107
+ Formula.parse('$A$1=$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"="],[:cell,'$A$2']]]
108
+ Formula.parse('$A$1>=$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,">="],[:cell,'$A$2']]]
109
+ Formula.parse('$A$1<=$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"<="],[:cell,'$A$2']]]
110
+ Formula.parse('$A$1<>$A$2').to_ast.should == [:formula,[:comparison,[:cell,'$A$1'],[:comparator,"<>"],[:cell,'$A$2']]]
111
+ end
112
+
113
+ it "returns functions" do
114
+ Formula.parse('PI()').to_ast.should == [:formula,[:function,'PI']]
115
+ Formula.parse('ERR($A$1)').to_ast.should == [:formula,[:function,'ERR',[:cell,'$A$1']]]
116
+ Formula.parse('SUM($A$1,sheet1!$1:$1000,1)').to_ast.should == [:formula,[:function,'SUM',[:cell,'$A$1'],[:sheet_reference,'sheet1',[:row_range,'$1','$1000']],[:number,'1']]]
117
+ Formula.parse('IF(A2="Hello","hello",sheet1!B4)').to_ast.should == [:formula, [:function, "IF", [:comparison, [:cell, "A2"], [:comparator, "="], [:string, "Hello"]], [:string, "hello"], [:sheet_reference, "sheet1", [:cell, "B4"]]]]
118
+ end
119
+
120
+ it "returns fully qualified structured references (i.e., Table[column])" do
121
+ Formula.parse('DeptSales[Sale Amount]').to_ast.should == [:formula,[:table_reference,'DeptSales','Sale Amount']]
122
+ Formula.parse('DeptSales[[#Totals],[ColA]]').to_ast.should == [:formula,[:table_reference,'DeptSales','[#Totals],[ColA]']]
123
+ Formula.parse('IV.Outputs[Vector]').to_ast.should == [:formula,[:table_reference,'IV.Outputs','Vector']]
124
+ Formula.parse("I.b.Outputs[2007.0]").to_ast.should == [:formula,[:table_reference,'I.b.Outputs','2007.0']]
125
+ Formula.parse("INDEX(Global.Assumptions[Households], MATCH(F$321,Global.Assumptions[Year], 0))").to_ast.should == [:formula, [:function, "INDEX", [:table_reference, "Global.Assumptions", "Households"], [:function, "MATCH", [:cell, "F$321"], [:table_reference, "Global.Assumptions", "Year"], [:number, "0"]]]]
126
+ Formula.parse("MAX(-SUM(I.a.Inputs[2007])-F$80,0)").to_ast.should == [:formula, [:function, "MAX", [:arithmetic, [:prefix, "-", [:function, "SUM", [:table_reference, "I.a.Inputs", "2007"]]], [:operator, "-"], [:cell, "F$80"]], [:number, "0"]]]
127
+ end
128
+
129
+ it "returns booleans" do
130
+ Formula.parse("TRUE*FALSE").to_ast.should == [:formula,[:arithmetic,[:boolean_true],[:operator,'*'],[:boolean_false]]]
131
+ end
132
+
133
+ it "returns prefixes (+/-)" do
134
+ Formula.parse("-(3-1)").to_ast.should == [:formula, [:prefix, "-", [:brackets, [:arithmetic, [:number, "3"], [:operator, "-"], [:number, "1"]]]]]
135
+ end
136
+
137
+ it "returns local structured references (i.e., [column])" do
138
+ Formula.parse('[Sale Amount]').to_ast.should == [:formula,[:local_table_reference,'Sale Amount']]
139
+ end
140
+
141
+ it "returns named references" do
142
+ Formula.parse('EF.NaturalGas.N2O').to_ast.should == [:formula,[:named_reference,'EF.NaturalGas.N2O']]
143
+ end
144
+
145
+ it "returns infix modifiers in edge cases" do
146
+ complex = "(-'DUKES 09 (2.5)'!$B$25)"
147
+ Formula.parse(complex).to_ast.should == [:formula, [:brackets, [:prefix, "-", [:quoted_sheet_reference, "DUKES 09 (2.5)", [:cell, "$B$25"]]]]]
148
+ complex2 = %q{-(INDEX(INDIRECT(BI$19&"!Year.Matrix"),MATCH("Subtotal.Supply",INDIRECT(BI$19&"!Year.Modules"),0),MATCH("V.04",INDIRECT(BI$19&"!Year.Vectors"),0))+
149
+ INDEX(INDIRECT(BI$19&"!Year.Matrix"),MATCH("Subtotal.Consumption",INDIRECT(BI$19&"!Year.Modules"),0),MATCH("V.04",INDIRECT(BI$19&"!Year.Vectors"),0)))}
150
+ Formula.parse(complex2).to_ast.should == [:formula, [:prefix, "-", [:brackets, [:arithmetic, [:function, "INDEX", [:function, "INDIRECT", [:string_join, [:cell, "BI$19"], [:string, "!Year.Matrix"]]], [:function, "MATCH", [:string, "Subtotal.Supply"], [:function, "INDIRECT", [:string_join, [:cell, "BI$19"], [:string, "!Year.Modules"]]], [:number, "0"]], [:function, "MATCH", [:string, "V.04"], [:function, "INDIRECT", [:string_join, [:cell, "BI$19"], [:string, "!Year.Vectors"]]], [:number, "0"]]], [:operator, "+"], [:function, "INDEX", [:function, "INDIRECT", [:string_join, [:cell, "BI$19"], [:string, "!Year.Matrix"]]], [:function, "MATCH", [:string, "Subtotal.Consumption"], [:function, "INDIRECT", [:string_join, [:cell, "BI$19"], [:string, "!Year.Modules"]]], [:number, "0"]], [:function, "MATCH", [:string, "V.04"], [:function, "INDIRECT", [:string_join, [:cell, "BI$19"], [:string, "!Year.Vectors"]]], [:number, "0"]]]]]]]
151
+ Formula.parse("MAX(MIN(F121, -F22),0)").to_ast.should == [:formula, [:function, "MAX", [:function, "MIN", [:cell, "F121"], [:prefix, "-", [:cell, "F22"]]], [:number, "0"]]]
152
+ end
153
+
154
+ it "returns formulas with spaces" do
155
+ spaced = "(13.56 / 96935) * EF.IndustrialCoal.CO2 * GWP.CH4"
156
+ Formula.parse(spaced).to_ast.should == [:formula, [:arithmetic, [:brackets, [:arithmetic, [:number, "13.56"], [:operator, "/"], [:number, "96935"]]], [:operator, "*"], [:named_reference, "EF.IndustrialCoal.CO2"], [:operator, "*"], [:named_reference, "GWP.CH4"]]]
157
+ spaced2 ='"per " & Preferences.EnergyUnits'
158
+ Formula.parse(spaced2).to_ast.should == [:formula, [:string_join, [:string, "per "],[:named_reference, "Preferences.EnergyUnits"]]]
159
+ spaced3 = ' 0.00403/$F$76'
160
+ Formula.parse(spaced3).to_ast.should == [:formula, [:arithmetic, [:number, "0.00403"], [:operator, "/"], [:cell, "$F$76"]]]
161
+ spaced4 = "SUMIFS(INDEX($G$62:$J$73, , MATCH($E$11, $G$61:$J$61, 0)), $C$62:$C$73, $C195, $D$62:$D$73, $D195)"
162
+ Formula.parse(spaced4).to_ast.should == [:formula, [:function, "SUMIFS", [:function, "INDEX", [:area, "$G$62", "$J$73"], [:null], [:function, "MATCH", [:cell, "$E$11"], [:area, "$G$61", "$J$61"], [:number, "0"]]], [:area, "$C$62", "$C$73"], [:cell, "$C195"], [:area, "$D$62", "$D$73"], [:cell, "$D195"]]]
163
+ end
164
+
165
+ end