rubyfromexcel 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +22 -0
- data/bin/rubyfromexcel +20 -0
- data/examples/create_and_test_examples.rb +37 -0
- data/examples/ruby-versions/array-formulas-ruby/sheets/sheet1.rb +59 -0
- data/examples/ruby-versions/array-formulas-ruby/sheets/sheet2.rb +9 -0
- data/examples/ruby-versions/array-formulas-ruby/specs/sheet1_rspec.rb +156 -0
- data/examples/ruby-versions/array-formulas-ruby/specs/sheet2_rspec.rb +8 -0
- data/examples/ruby-versions/array-formulas-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/complex-test-ruby/sheets/sheet1.rb +305 -0
- data/examples/ruby-versions/complex-test-ruby/sheets/sheet2.rb +147 -0
- data/examples/ruby-versions/complex-test-ruby/specs/sheet1_rspec.rb +876 -0
- data/examples/ruby-versions/complex-test-ruby/specs/sheet2_rspec.rb +412 -0
- data/examples/ruby-versions/complex-test-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet1.rb +9 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet2.rb +8 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet1_rspec.rb +16 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet2_rspec.rb +16 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/pruning-ruby/sheets/sheet1.rb +11 -0
- data/examples/ruby-versions/pruning-ruby/sheets/sheet2.rb +14 -0
- data/examples/ruby-versions/pruning-ruby/sheets/sheet3.rb +7 -0
- data/examples/ruby-versions/pruning-ruby/specs/sheet1_rspec.rb +20 -0
- data/examples/ruby-versions/pruning-ruby/specs/sheet2_rspec.rb +20 -0
- data/examples/ruby-versions/pruning-ruby/specs/sheet3_rspec.rb +8 -0
- data/examples/ruby-versions/pruning-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/sharedFormulaTest-ruby/sheets/sheet1.rb +15 -0
- data/examples/ruby-versions/sharedFormulaTest-ruby/specs/sheet1_rspec.rb +44 -0
- data/examples/ruby-versions/sharedFormulaTest-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/table-test-ruby/sheets/sheet1.rb +17 -0
- data/examples/ruby-versions/table-test-ruby/sheets/sheet2.rb +5 -0
- data/examples/ruby-versions/table-test-ruby/sheets/sheet3.rb +5 -0
- data/examples/ruby-versions/table-test-ruby/specs/sheet1_rspec.rb +20 -0
- data/examples/ruby-versions/table-test-ruby/specs/sheet2_rspec.rb +8 -0
- data/examples/ruby-versions/table-test-ruby/specs/sheet3_rspec.rb +8 -0
- data/examples/ruby-versions/table-test-ruby/spreadsheet.rb +9 -0
- data/examples/sheets/array-formulas.xlsx +0 -0
- data/examples/sheets/complex-test.xlsx +0 -0
- data/examples/sheets/namedReferenceTest.xlsx +0 -0
- data/examples/sheets/pruning.xlsx +0 -0
- data/examples/sheets/sharedFormulaTest.xlsx +0 -0
- data/examples/sheets/table-test.xlsx +0 -0
- data/examples/sheets/~$array-formulas.xlsx +0 -0
- data/examples/unzipped-sheets/array-formulas/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/array-formulas/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/array-formulas/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/complex-test/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/complex-test/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/complex-test/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/charts/chart1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/charts/chart2.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/comments1.xml +5 -0
- data/examples/unzipped-sheets/complex-test/xl/comments2.xml +5 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing1.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing2.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/drawing1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/drawing2.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing1.vml +46 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing2.vml +46 -0
- data/examples/unzipped-sheets/complex-test/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet2.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/pruning/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/pruning/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/pruning/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/pruning/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/pruning/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/pruning/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/worksheets/sheet3.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/table-test/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/table-test/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/table-test/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/table-test/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/printerSettings/printerSettings1.bin +0 -0
- data/examples/unzipped-sheets/table-test/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/tables/table1.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/sheet3.xml +2 -0
- data/lib/cells/array/array_formula_builder.rb +58 -0
- data/lib/cells/array/array_formula_cell.rb +27 -0
- data/lib/cells/array/arraying_formula_cell.rb +67 -0
- data/lib/cells/array/single_cell_array_formula_builder.rb +9 -0
- data/lib/cells/array/single_cell_array_formula_cell.rb +11 -0
- data/lib/cells/cell.rb +98 -0
- data/lib/cells/cells.rb +9 -0
- data/lib/cells/formula/formula_cell.rb +18 -0
- data/lib/cells/formula/simple_formula_cell.rb +4 -0
- data/lib/cells/shared/shared_formula_builder.rb +15 -0
- data/lib/cells/shared/shared_formula_cell.rb +20 -0
- data/lib/cells/shared/sharing_formula_cell.rb +36 -0
- data/lib/cells/value/value_cell.rb +24 -0
- data/lib/excelfile/excelfile.rb +6 -0
- data/lib/excelfile/relationships.rb +24 -0
- data/lib/excelfile/shared_strings.rb +21 -0
- data/lib/excelfile/sheet_names.rb +6 -0
- data/lib/excelfile/table.rb +116 -0
- data/lib/excelfile/workbook.rb +108 -0
- data/lib/excelfile/worksheet.rb +122 -0
- data/lib/formulae/compile/formula_builder.rb +316 -0
- data/lib/formulae/formulae.rb +6 -0
- data/lib/formulae/parse/formula_peg.rb +213 -0
- data/lib/formulae/parse/formula_peg.txt +40 -0
- data/lib/formulae/run/excel_functions.rb +375 -0
- data/lib/formulae/run/excel_matrix.rb +114 -0
- data/lib/formulae/run/excel_range.rb +256 -0
- data/lib/formulae/run/reference.rb +79 -0
- data/lib/optimiser/dependency_builder.rb +86 -0
- data/lib/optimiser/optimiser.rb +3 -0
- data/lib/optimiser/shared_formula_dependency_builder.rb +43 -0
- data/lib/optimiser/workbook_pruner.rb +80 -0
- data/lib/rubyfromexcel.rb +105 -0
- data/lib/runtime/runtime_formula_builder.rb +32 -0
- data/spec/array_formula_builder_spec.rb +35 -0
- data/spec/array_formula_cell_spec.rb +17 -0
- data/spec/arraying_formula_cell_spec.rb +38 -0
- data/spec/dependency_builder_spec.rb +71 -0
- data/spec/excel_functions_spec.rb +381 -0
- data/spec/excel_matrix_spec.rb +92 -0
- data/spec/excel_range_spec.rb +161 -0
- data/spec/formula_builder_spec.rb +230 -0
- data/spec/formula_peg_spec.rb +165 -0
- data/spec/reference_spec.rb +72 -0
- data/spec/relationships_spec.rb +51 -0
- data/spec/runtime_formula_builder_spec.rb +55 -0
- data/spec/shared_formula_builder_spec.rb +29 -0
- data/spec/shared_formula_cell_spec.rb +23 -0
- data/spec/shared_formula_dependency_builder_spec.rb +48 -0
- data/spec/shared_strings_spec.rb +14 -0
- data/spec/sharing_formula_cell_spec.rb +79 -0
- data/spec/simple_formula_cell_spec.rb +78 -0
- data/spec/single_cell_array_formula_builder_spec.rb +19 -0
- data/spec/single_cell_array_formula_cell_spec.rb +25 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/table_spec.rb +100 -0
- data/spec/value_cell_spec.rb +49 -0
- data/spec/workbook_pruner_spec.rb +27 -0
- data/spec/workbook_spec.rb +283 -0
- data/spec/worksheet_failiures_spec.rb +41 -0
- data/spec/worksheet_spec.rb +486 -0
- 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
|