rubyfromexcel 0.0.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 (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