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