rubyfromexcel 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/README +22 -0
  2. data/bin/rubyfromexcel +20 -0
  3. data/examples/create_and_test_examples.rb +37 -0
  4. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet1.rb +59 -0
  5. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet2.rb +9 -0
  6. data/examples/ruby-versions/array-formulas-ruby/specs/sheet1_rspec.rb +156 -0
  7. data/examples/ruby-versions/array-formulas-ruby/specs/sheet2_rspec.rb +8 -0
  8. data/examples/ruby-versions/array-formulas-ruby/spreadsheet.rb +9 -0
  9. data/examples/ruby-versions/complex-test-ruby/sheets/sheet1.rb +305 -0
  10. data/examples/ruby-versions/complex-test-ruby/sheets/sheet2.rb +147 -0
  11. data/examples/ruby-versions/complex-test-ruby/specs/sheet1_rspec.rb +876 -0
  12. data/examples/ruby-versions/complex-test-ruby/specs/sheet2_rspec.rb +412 -0
  13. data/examples/ruby-versions/complex-test-ruby/spreadsheet.rb +9 -0
  14. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet1.rb +9 -0
  15. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet2.rb +8 -0
  16. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet1_rspec.rb +16 -0
  17. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet2_rspec.rb +16 -0
  18. data/examples/ruby-versions/namedReferenceTest-ruby/spreadsheet.rb +9 -0
  19. data/examples/ruby-versions/pruning-ruby/sheets/sheet1.rb +11 -0
  20. data/examples/ruby-versions/pruning-ruby/sheets/sheet2.rb +14 -0
  21. data/examples/ruby-versions/pruning-ruby/sheets/sheet3.rb +7 -0
  22. data/examples/ruby-versions/pruning-ruby/specs/sheet1_rspec.rb +20 -0
  23. data/examples/ruby-versions/pruning-ruby/specs/sheet2_rspec.rb +20 -0
  24. data/examples/ruby-versions/pruning-ruby/specs/sheet3_rspec.rb +8 -0
  25. data/examples/ruby-versions/pruning-ruby/spreadsheet.rb +9 -0
  26. data/examples/ruby-versions/sharedFormulaTest-ruby/sheets/sheet1.rb +15 -0
  27. data/examples/ruby-versions/sharedFormulaTest-ruby/specs/sheet1_rspec.rb +44 -0
  28. data/examples/ruby-versions/sharedFormulaTest-ruby/spreadsheet.rb +9 -0
  29. data/examples/ruby-versions/table-test-ruby/sheets/sheet1.rb +17 -0
  30. data/examples/ruby-versions/table-test-ruby/sheets/sheet2.rb +5 -0
  31. data/examples/ruby-versions/table-test-ruby/sheets/sheet3.rb +5 -0
  32. data/examples/ruby-versions/table-test-ruby/specs/sheet1_rspec.rb +20 -0
  33. data/examples/ruby-versions/table-test-ruby/specs/sheet2_rspec.rb +8 -0
  34. data/examples/ruby-versions/table-test-ruby/specs/sheet3_rspec.rb +8 -0
  35. data/examples/ruby-versions/table-test-ruby/spreadsheet.rb +9 -0
  36. data/examples/sheets/array-formulas.xlsx +0 -0
  37. data/examples/sheets/complex-test.xlsx +0 -0
  38. data/examples/sheets/namedReferenceTest.xlsx +0 -0
  39. data/examples/sheets/pruning.xlsx +0 -0
  40. data/examples/sheets/sharedFormulaTest.xlsx +0 -0
  41. data/examples/sheets/table-test.xlsx +0 -0
  42. data/examples/sheets/~$array-formulas.xlsx +0 -0
  43. data/examples/unzipped-sheets/array-formulas/[Content_Types].xml +2 -0
  44. data/examples/unzipped-sheets/array-formulas/docProps/app.xml +2 -0
  45. data/examples/unzipped-sheets/array-formulas/docProps/core.xml +2 -0
  46. data/examples/unzipped-sheets/array-formulas/docProps/thumbnail.jpeg +0 -0
  47. data/examples/unzipped-sheets/array-formulas/xl/_rels/workbook.xml.rels +2 -0
  48. data/examples/unzipped-sheets/array-formulas/xl/calcChain.xml +2 -0
  49. data/examples/unzipped-sheets/array-formulas/xl/sharedStrings.xml +2 -0
  50. data/examples/unzipped-sheets/array-formulas/xl/styles.xml +2 -0
  51. data/examples/unzipped-sheets/array-formulas/xl/theme/theme1.xml +2 -0
  52. data/examples/unzipped-sheets/array-formulas/xl/workbook.xml +2 -0
  53. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet1.xml +2 -0
  54. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet2.xml +2 -0
  55. data/examples/unzipped-sheets/complex-test/[Content_Types].xml +2 -0
  56. data/examples/unzipped-sheets/complex-test/docProps/app.xml +2 -0
  57. data/examples/unzipped-sheets/complex-test/docProps/core.xml +2 -0
  58. data/examples/unzipped-sheets/complex-test/xl/_rels/workbook.xml.rels +2 -0
  59. data/examples/unzipped-sheets/complex-test/xl/calcChain.xml +2 -0
  60. data/examples/unzipped-sheets/complex-test/xl/charts/chart1.xml +2 -0
  61. data/examples/unzipped-sheets/complex-test/xl/charts/chart2.xml +2 -0
  62. data/examples/unzipped-sheets/complex-test/xl/comments1.xml +5 -0
  63. data/examples/unzipped-sheets/complex-test/xl/comments2.xml +5 -0
  64. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing1.xml.rels +2 -0
  65. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing2.xml.rels +2 -0
  66. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing1.xml +2 -0
  67. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing2.xml +2 -0
  68. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing1.vml +46 -0
  69. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing2.vml +46 -0
  70. data/examples/unzipped-sheets/complex-test/xl/sharedStrings.xml +2 -0
  71. data/examples/unzipped-sheets/complex-test/xl/styles.xml +2 -0
  72. data/examples/unzipped-sheets/complex-test/xl/theme/theme1.xml +2 -0
  73. data/examples/unzipped-sheets/complex-test/xl/workbook.xml +2 -0
  74. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  75. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet2.xml.rels +2 -0
  76. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet1.xml +2 -0
  77. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet2.xml +2 -0
  78. data/examples/unzipped-sheets/namedReferenceTest/[Content_Types].xml +2 -0
  79. data/examples/unzipped-sheets/namedReferenceTest/docProps/app.xml +2 -0
  80. data/examples/unzipped-sheets/namedReferenceTest/docProps/core.xml +2 -0
  81. data/examples/unzipped-sheets/namedReferenceTest/docProps/thumbnail.jpeg +0 -0
  82. data/examples/unzipped-sheets/namedReferenceTest/xl/_rels/workbook.xml.rels +2 -0
  83. data/examples/unzipped-sheets/namedReferenceTest/xl/calcChain.xml +2 -0
  84. data/examples/unzipped-sheets/namedReferenceTest/xl/styles.xml +2 -0
  85. data/examples/unzipped-sheets/namedReferenceTest/xl/theme/theme1.xml +2 -0
  86. data/examples/unzipped-sheets/namedReferenceTest/xl/workbook.xml +2 -0
  87. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet1.xml +2 -0
  88. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet2.xml +2 -0
  89. data/examples/unzipped-sheets/pruning/[Content_Types].xml +2 -0
  90. data/examples/unzipped-sheets/pruning/docProps/app.xml +2 -0
  91. data/examples/unzipped-sheets/pruning/docProps/core.xml +2 -0
  92. data/examples/unzipped-sheets/pruning/docProps/thumbnail.jpeg +0 -0
  93. data/examples/unzipped-sheets/pruning/xl/_rels/workbook.xml.rels +2 -0
  94. data/examples/unzipped-sheets/pruning/xl/calcChain.xml +2 -0
  95. data/examples/unzipped-sheets/pruning/xl/sharedStrings.xml +2 -0
  96. data/examples/unzipped-sheets/pruning/xl/styles.xml +2 -0
  97. data/examples/unzipped-sheets/pruning/xl/theme/theme1.xml +2 -0
  98. data/examples/unzipped-sheets/pruning/xl/workbook.xml +2 -0
  99. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet1.xml +2 -0
  100. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet2.xml +2 -0
  101. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet3.xml +2 -0
  102. data/examples/unzipped-sheets/sharedFormulaTest/[Content_Types].xml +2 -0
  103. data/examples/unzipped-sheets/sharedFormulaTest/docProps/app.xml +2 -0
  104. data/examples/unzipped-sheets/sharedFormulaTest/docProps/core.xml +2 -0
  105. data/examples/unzipped-sheets/sharedFormulaTest/docProps/thumbnail.jpeg +0 -0
  106. data/examples/unzipped-sheets/sharedFormulaTest/xl/_rels/workbook.xml.rels +2 -0
  107. data/examples/unzipped-sheets/sharedFormulaTest/xl/calcChain.xml +2 -0
  108. data/examples/unzipped-sheets/sharedFormulaTest/xl/styles.xml +2 -0
  109. data/examples/unzipped-sheets/sharedFormulaTest/xl/theme/theme1.xml +2 -0
  110. data/examples/unzipped-sheets/sharedFormulaTest/xl/workbook.xml +2 -0
  111. data/examples/unzipped-sheets/sharedFormulaTest/xl/worksheets/sheet1.xml +2 -0
  112. data/examples/unzipped-sheets/table-test/[Content_Types].xml +2 -0
  113. data/examples/unzipped-sheets/table-test/docProps/app.xml +2 -0
  114. data/examples/unzipped-sheets/table-test/docProps/core.xml +2 -0
  115. data/examples/unzipped-sheets/table-test/xl/_rels/workbook.xml.rels +2 -0
  116. data/examples/unzipped-sheets/table-test/xl/calcChain.xml +2 -0
  117. data/examples/unzipped-sheets/table-test/xl/printerSettings/printerSettings1.bin +0 -0
  118. data/examples/unzipped-sheets/table-test/xl/sharedStrings.xml +2 -0
  119. data/examples/unzipped-sheets/table-test/xl/styles.xml +2 -0
  120. data/examples/unzipped-sheets/table-test/xl/tables/table1.xml +2 -0
  121. data/examples/unzipped-sheets/table-test/xl/theme/theme1.xml +2 -0
  122. data/examples/unzipped-sheets/table-test/xl/workbook.xml +2 -0
  123. data/examples/unzipped-sheets/table-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  124. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet1.xml +2 -0
  125. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet2.xml +2 -0
  126. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet3.xml +2 -0
  127. data/lib/cells/array/array_formula_builder.rb +58 -0
  128. data/lib/cells/array/array_formula_cell.rb +27 -0
  129. data/lib/cells/array/arraying_formula_cell.rb +67 -0
  130. data/lib/cells/array/single_cell_array_formula_builder.rb +9 -0
  131. data/lib/cells/array/single_cell_array_formula_cell.rb +11 -0
  132. data/lib/cells/cell.rb +98 -0
  133. data/lib/cells/cells.rb +9 -0
  134. data/lib/cells/formula/formula_cell.rb +18 -0
  135. data/lib/cells/formula/simple_formula_cell.rb +4 -0
  136. data/lib/cells/shared/shared_formula_builder.rb +15 -0
  137. data/lib/cells/shared/shared_formula_cell.rb +20 -0
  138. data/lib/cells/shared/sharing_formula_cell.rb +36 -0
  139. data/lib/cells/value/value_cell.rb +24 -0
  140. data/lib/excelfile/excelfile.rb +6 -0
  141. data/lib/excelfile/relationships.rb +24 -0
  142. data/lib/excelfile/shared_strings.rb +21 -0
  143. data/lib/excelfile/sheet_names.rb +6 -0
  144. data/lib/excelfile/table.rb +116 -0
  145. data/lib/excelfile/workbook.rb +108 -0
  146. data/lib/excelfile/worksheet.rb +122 -0
  147. data/lib/formulae/compile/formula_builder.rb +316 -0
  148. data/lib/formulae/formulae.rb +6 -0
  149. data/lib/formulae/parse/formula_peg.rb +213 -0
  150. data/lib/formulae/parse/formula_peg.txt +40 -0
  151. data/lib/formulae/run/excel_functions.rb +375 -0
  152. data/lib/formulae/run/excel_matrix.rb +114 -0
  153. data/lib/formulae/run/excel_range.rb +256 -0
  154. data/lib/formulae/run/reference.rb +79 -0
  155. data/lib/optimiser/dependency_builder.rb +86 -0
  156. data/lib/optimiser/optimiser.rb +3 -0
  157. data/lib/optimiser/shared_formula_dependency_builder.rb +43 -0
  158. data/lib/optimiser/workbook_pruner.rb +80 -0
  159. data/lib/rubyfromexcel.rb +105 -0
  160. data/lib/runtime/runtime_formula_builder.rb +32 -0
  161. data/spec/array_formula_builder_spec.rb +35 -0
  162. data/spec/array_formula_cell_spec.rb +17 -0
  163. data/spec/arraying_formula_cell_spec.rb +38 -0
  164. data/spec/dependency_builder_spec.rb +71 -0
  165. data/spec/excel_functions_spec.rb +381 -0
  166. data/spec/excel_matrix_spec.rb +92 -0
  167. data/spec/excel_range_spec.rb +161 -0
  168. data/spec/formula_builder_spec.rb +230 -0
  169. data/spec/formula_peg_spec.rb +165 -0
  170. data/spec/reference_spec.rb +72 -0
  171. data/spec/relationships_spec.rb +51 -0
  172. data/spec/runtime_formula_builder_spec.rb +55 -0
  173. data/spec/shared_formula_builder_spec.rb +29 -0
  174. data/spec/shared_formula_cell_spec.rb +23 -0
  175. data/spec/shared_formula_dependency_builder_spec.rb +48 -0
  176. data/spec/shared_strings_spec.rb +14 -0
  177. data/spec/sharing_formula_cell_spec.rb +79 -0
  178. data/spec/simple_formula_cell_spec.rb +78 -0
  179. data/spec/single_cell_array_formula_builder_spec.rb +19 -0
  180. data/spec/single_cell_array_formula_cell_spec.rb +25 -0
  181. data/spec/spec_helper.rb +2 -0
  182. data/spec/table_spec.rb +100 -0
  183. data/spec/value_cell_spec.rb +49 -0
  184. data/spec/workbook_pruner_spec.rb +27 -0
  185. data/spec/workbook_spec.rb +283 -0
  186. data/spec/worksheet_failiures_spec.rb +41 -0
  187. data/spec/worksheet_spec.rb +486 -0
  188. metadata +291 -0
@@ -0,0 +1,105 @@
1
+ # Standard library
2
+ require 'fileutils'
3
+ require 'singleton'
4
+
5
+ # Gems
6
+ require 'nokogiri'
7
+ require 'rubyscriptwriter'
8
+ require 'rubyspecwriter'
9
+ require 'rubypeg'
10
+
11
+ # Components of RubyFromExcel
12
+ require_relative 'excelfile/excelfile'
13
+ require_relative 'formulae/formulae'
14
+ require_relative 'cells/cells'
15
+ require_relative 'optimiser/optimiser'
16
+
17
+ require_relative 'runtime/runtime_formula_builder'
18
+
19
+ module RubyFromExcel
20
+ class Process
21
+ attr_accessor :source_excel_directory
22
+ attr_accessor :target_ruby_directory
23
+ attr_accessor :workbook
24
+ attr_accessor :skip_tests
25
+ attr_accessor :prune_except_output_sheets
26
+ attr_accessor :convert_independent_of_input_sheets
27
+
28
+ def initialize(&block)
29
+ instance_eval(&block) if block
30
+ end
31
+
32
+ def workbook_filename
33
+ File.join(source_excel_directory,'xl','workbook.xml')
34
+ end
35
+
36
+ def start!
37
+ reset_global_classes
38
+
39
+ time "Preparing destination folder..." do
40
+ prepare_destination_folder
41
+ end
42
+
43
+ time "Loading..." do
44
+ self.workbook = Workbook.new(workbook_filename)
45
+ end
46
+
47
+ if prune_except_output_sheets
48
+ time "Pruning..." do
49
+ workbook.prune_cells_not_needed_for_output_sheets(*prune_except_output_sheets)
50
+ workbook.convert_cells_to_values_when_independent_of_input_sheets(*convert_independent_of_input_sheets)
51
+ end
52
+ end
53
+
54
+ time "Workbook contains #{workbook.worksheets.size} sheets:\n" do
55
+
56
+ puts "0) Generating ruby for the workbook"
57
+ write workbook.to_ruby, :to, "spreadsheet.rb"
58
+
59
+ i = 0
60
+ workbook.worksheets.each do |variable_name, worksheet|
61
+ time "#{i+=1}) Generating ruby for #{variable_name}..." do
62
+ print 'ruby...'
63
+ write worksheet.to_ruby, :to, 'sheets', "#{variable_name}.rb"
64
+ unless skip_tests
65
+ print 'test...'
66
+ write worksheet.to_test, :to, 'specs',"#{variable_name}_rspec.rb"
67
+ end
68
+ # worksheet.nil_memory_consuming_variables!
69
+ end
70
+ end
71
+
72
+ end
73
+ unless skip_tests
74
+ puts "Running tests of generated files"
75
+ puts `spec -fo #{File.join(target_ruby_directory,'specs',"*")}`
76
+ end
77
+ end
78
+
79
+ # FIXME: Urgh. Global variables. Need to eliminate these!
80
+ def reset_global_classes
81
+ SheetNames.instance.clear
82
+ SharedStrings.instance.clear
83
+ end
84
+
85
+ def prepare_destination_folder
86
+ FileUtils.mkpath(File.join(target_ruby_directory,'specs'))
87
+ FileUtils.mkpath(File.join(target_ruby_directory,'sheets'))
88
+ end
89
+
90
+ def write(thing,to,*filenames)
91
+ File.open(File.join(target_ruby_directory,*filenames),'w') do |f|
92
+ f.puts thing.to_s
93
+ end
94
+ end
95
+
96
+ def time(message)
97
+ print message
98
+ STDOUT.flush
99
+ start_time = Time.now
100
+ yield
101
+ puts "took #{(Time.now-start_time).to_i} seconds."
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,32 @@
1
+ module RubyFromExcel
2
+
3
+ class RuntimeFormulaBuilder < FormulaBuilder
4
+
5
+ attr_accessor :worksheet, :refering_cell_reference
6
+
7
+ def initialize(worksheet, refering_cell_reference = nil)
8
+ self.worksheet = worksheet
9
+ self.refering_cell_reference = refering_cell_reference
10
+ end
11
+
12
+ def sheet_reference(sheet_name,reference)
13
+ "s('#{sheet_name}').#{reference.visit(self)}"
14
+ end
15
+
16
+ alias :quoted_sheet_reference :sheet_reference
17
+
18
+ def table_reference(table_name,structured_reference)
19
+ table = worksheet.t(table_name)
20
+ table.respond_to?(:reference_for) ? table.reference_for(structured_reference,refering_cell_reference).to_s : table
21
+ end
22
+
23
+ def named_reference(name)
24
+ name.to_method_name
25
+ end
26
+
27
+ def indirect_function(text_formula)
28
+ "indirect(#{text_formula.visit(self)},'#{formula_cell && formula_cell.reference}')"
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe ArrayFormulaBuilder, "Formulas with shared_formula_offsets" do
4
+
5
+ before(:each) do
6
+ @builder = ArrayFormulaBuilder.new
7
+ end
8
+
9
+ def ruby_for(formula)
10
+ ast = Formula.parse(formula)
11
+ ast.visit(@builder)
12
+ end
13
+
14
+ it "should leave individual references alone" do
15
+ ruby_for("A1").should == "a1"
16
+ ruby_for("A$1").should == "a1"
17
+ ruby_for("$A1").should == "a1"
18
+ ruby_for("$A$1").should == "a1"
19
+ end
20
+
21
+ it "should replace range references with individual cell references" do
22
+ ruby_for("A:C").should == "c('a','c')"
23
+ ruby_for("1:10").should == "r(1,10)"
24
+ ruby_for("IF(A1:A5>0,A1:A5,1+2)").should == "m(m(a('a1','a5'),0.0) { |r1,r2| r1>r2 },a('a1','a5'),m(1.0,2.0) { |r1,r2| r1+r2 }) { |r1,r2,r3| excel_if(r1,r2,r3) }"
25
+ ruby_for("B10:B20+B5").should == "m(a('b10','b20'),b5) { |r1,r2| r1+r2 }"
26
+ end
27
+
28
+ it "should not replace range references where they are expected by the formula" do
29
+ ruby_for("INDEX($F$16:$I$19, ,MATCH($E$8, $F$15:$I$15, 0))").should == "m(0.0,m(e8,0.0) { |r1,r2| match(r1,a('f15','i15'),r2) }) { |r1,r2| index(a('f16','i19'),r1,r2) }"
30
+ ruby_for("SUMIF($F$16:$I$19,C8)").should == "m(c8) { |r1| sumif(a('f16','i19'),r1) }"
31
+ ruby_for("SUMIF($F$16:$I$19,C8,$Q$16:$R$19)").should == "m(c8) { |r1| sumif(a('f16','i19'),r1,a('q16','r19')) }"
32
+ ruby_for("SUMIFS($F$16:$F$19,$G$16:$G$19,1.0,$H$16:$H$19,Q1:Q4)").should == "m(1.0,a('q1','q4')) { |r1,r2| sumifs(a('f16','f19'),a('g16','g19'),r1,a('h16','h19'),r2) }"
33
+ end
34
+
35
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe ArrayFormulaCell do
4
+
5
+ # <c r="B3"><f t="array" ref="B3:E6">B2:E2+A3:A6</f><v>2</v></c>
6
+
7
+ it "it is given a value cell and a pre-parsed formula and picks out values from its array references according to array_formula_offset" do
8
+ value_cell = ValueCell.new(mock('worksheet',:to_s => 'sheet1'),Nokogiri::XML('<c r="D6"><v>7</v></c>').root)
9
+
10
+ cell = ArrayFormulaCell.from_other_cell(value_cell)
11
+ cell.array_formula_reference = "b3_array"
12
+ cell.array_formula_offset = [1,1]
13
+ cell.to_ruby.should == "def d6; @d6 ||= b3_array.array_formula_offset(1,1); end\n"
14
+ cell.to_test.should == "it 'cell d6 should equal 7.0' do\n sheet1.d6.should be_close(7.0,0.7)\nend\n\n"
15
+ end
16
+
17
+ end
@@ -0,0 +1,38 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe ArrayingFormulaCell do
4
+
5
+ # <c r="B3"><f t="array" ref="B3:E6">B2:E2+A3:A6</f><v>2</v></c>
6
+
7
+ before do
8
+ @worksheet = mock(:worksheet,:class_name => 'Sheet1', :to_s => 'sheet1')
9
+
10
+ @arraying_cell = ArrayingFormulaCell.new(
11
+ @worksheet,
12
+ Nokogiri::XML('<c r="B3"><f t="array" ref="B3:C3">D2:E2+A3:A6</f><v>2</v></c>').root
13
+ )
14
+ @value_cell = ValueCell.new(
15
+ @worksheet,
16
+ Nokogiri::XML('<c r="C3"><v>7</v></c>').root
17
+ )
18
+ @worksheet.should_receive(:cell).with('c3').and_return(@value_cell)
19
+ @worksheet.should_receive(:replace_cell) do |reference,new_cell|
20
+ reference.should == 'c3'
21
+ new_cell.to_ruby.should == "def c3; @c3 ||= b3_array.array_formula_offset(0,1); end\n"
22
+ end
23
+ @arraying_cell.alter_other_cells_if_required
24
+ end
25
+
26
+ it "it is given a pre-parsed formula and picks out values from its array references according to array_formula_offset" do
27
+ @arraying_cell.to_ruby.should == "def b3_array; @b3_array ||= m(a('d2','e2'),a('a3','a6')) { |r1,r2| r1+r2 }; end\ndef b3; @b3 ||= b3_array.array_formula_offset(0,0); end\n"
28
+ end
29
+
30
+ it "should know its dependencies, and also apply them to the cells that it arrays with" do
31
+ array_cell = mock(:array_cell)
32
+ array_cell.should_receive(:dependencies=).with(["sheet1.a3", "sheet1.a4", "sheet1.a5", "sheet1.a6", "sheet1.d2", "sheet1.e2"])
33
+ @worksheet.should_receive(:cell).with('c3').and_return(array_cell)
34
+ @arraying_cell.work_out_dependencies
35
+ @arraying_cell.dependencies.should == ["sheet1.a3", "sheet1.a4", "sheet1.a5", "sheet1.a6", "sheet1.d2", "sheet1.e2"]
36
+ end
37
+
38
+ end
@@ -0,0 +1,71 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe DependencyBuilder do
4
+
5
+ before(:each) do
6
+ SheetNames.instance.clear
7
+ SheetNames.instance['Other Sheet'] = 'sheet2'
8
+ @workbook = mock(:workbook, :named_references => {'named_cell' => 'sheet2.z10', 'named_cell2' => "sheet2.a('z10','ab10')"})
9
+ @worksheet1 = mock(:worksheet, :to_s => 'sheet1', :workbook => @workbook, :named_references => {'named_cell' => 'sheet1.a1'})
10
+ @worksheet2 = mock(:worksheet, :to_s => 'sheet2', :workbook => @workbook, :named_references => {})
11
+ @workbook.stub!(:worksheets => {'sheet1' => @worksheet1, 'sheet2' => @worksheet2 })
12
+ @cell = mock(:cell,:worksheet => @worksheet1, :reference => Reference.new('c30',@worksheet1))
13
+ @builder = DependencyBuilder.new(@cell)
14
+ end
15
+
16
+ def dependencies_for(formula)
17
+ ast = Formula.parse(formula)
18
+ ast.visit(@builder)
19
+ end
20
+
21
+ it "should know about single dependencies to referred cells" do
22
+ dependencies_for("A1").should == ['sheet1.a1']
23
+ end
24
+
25
+ it "should know about multiple dependencies to referred cells " do
26
+ dependencies_for("A1+(B2*A1)").should == ['sheet1.a1','sheet1.b2']
27
+ end
28
+
29
+ it "should know about dependences to other worksheets" do
30
+ dependencies_for("A1+'Other Sheet'!A1").should == ['sheet1.a1','sheet2.a1']
31
+ end
32
+
33
+ it "should know about dependences in ranges" do
34
+ dependencies_for("A1:A3").should == ['sheet1.a1','sheet1.a2','sheet1.a3']
35
+ end
36
+
37
+ it "should know about dependences in ranges on other sheets" do
38
+ dependencies_for("'Other Sheet'!A1:A3").should == ['sheet2.a1','sheet2.a2','sheet2.a3']
39
+ end
40
+
41
+ it "should know about dependencies in named ranges" do
42
+ dependencies_for("named_cell").should == ['sheet1.a1']
43
+ end
44
+
45
+ it "should know about dependencies in named ranges on other sheets" do
46
+ dependencies_for("'Other Sheet'!named_cell").should == ['sheet2.z10']
47
+ dependencies_for("'Other Sheet'!named_cell2").should == ['sheet2.aa10','sheet2.ab10','sheet2.z10']
48
+ end
49
+
50
+ it "should know about dependencies created by table references" do
51
+ Table.new(@worksheet2,'Vectors','a1:b2',['ColA','Description'],1)
52
+ dependencies_for("Vectors[#all]").should == ["sheet2.a1","sheet2.a2",'sheet2.b1','sheet2.b2']
53
+ end
54
+
55
+ it "should know about dependencies created by table references provided without table names" do
56
+ Table.new(@worksheet1,'Vectors','a1:c41',['ColA','Description','ColC'],1)
57
+ dependencies_for("[Description]").should == ["sheet1.b30"]
58
+ end
59
+
60
+ it "should know about dependencies created by indirect formulae" do
61
+ Table.new(@worksheet2,'IndirectVectors','a1:b2',['ColA','Description'],1)
62
+ dependencies_for('INDIRECT("IndirectVectors[#all]")').should == ["sheet2.a1","sheet2.a2",'sheet2.b1','sheet2.b2']
63
+ end
64
+
65
+ it "and be able to deal with indirect formulae that call upon other vectors" do
66
+ Table.new(@worksheet2,'IndirectVectors2','a1:b10',['ColA','Description'],1)
67
+ @worksheet1.should_receive(:cell).with('c1').and_return(mock(:cell,:value_for_including => 'ColA',:can_be_replaced_with_value? => true))
68
+ dependencies_for('INDIRECT("IndirectVectors2["&C1&"]")').should == ["sheet1.c1","sheet2.a2", "sheet2.a3", "sheet2.a4", "sheet2.a5", "sheet2.a6", "sheet2.a7", "sheet2.a8", "sheet2.a9"]
69
+ end
70
+
71
+ end
@@ -0,0 +1,381 @@
1
+ require_relative 'spec_helper'
2
+
3
+ class FunctionTest
4
+ extend ExcelFunctions
5
+ def self.a1; 10.0; end
6
+ def self.a2; 100.0; end
7
+ def self.b1; 'Pear'; end
8
+ def self.b2; 'Bear'; end
9
+ def self.b3; 'Apple'; end
10
+ def self.zx10; 2.0; end
11
+ def self.zy10; 3.0; end
12
+ def self.zz10; 4.0; end
13
+ end
14
+
15
+ describe "set" do
16
+ it "should set the value of existing cells" do
17
+ class ValueSetTest
18
+ include ExcelFunctions
19
+ def a1; 10.0; end
20
+ end
21
+ v = ValueSetTest.new
22
+ v.a1.should == 10.0
23
+ v.set('a1',99.0)
24
+ v.a1.should == 99.0
25
+ v.set('a1',nil)
26
+ v.a1.should == 10.0
27
+ end
28
+ end
29
+
30
+ describe "sum" do
31
+ it "should total areas correctly" do
32
+ FunctionTest.sum(1,2,3).should == 6
33
+ FunctionTest.sum(FunctionTest.a('a1','a2'),3).should == 113
34
+ end
35
+ end
36
+
37
+ describe "choose" do
38
+ it "should return whichever argument is matched by the first argument" do
39
+ FunctionTest.choose(1,1,2,3,4).should == 1
40
+ FunctionTest.choose(2,1,2,3,4).should == 2
41
+ FunctionTest.choose(3,1,2,3,4).should == 3
42
+ end
43
+ end
44
+
45
+ describe "abs" do
46
+ it "should return the absolute value of the input" do
47
+ FunctionTest.abs(1.0).should == 1
48
+ FunctionTest.abs(-1.0).should == 1.0
49
+ end
50
+ end
51
+
52
+ describe "sumif" do
53
+ it "should only sum values in the area that meet the criteria" do
54
+ FunctionTest.sumif(FunctionTest.a('a1','a3'),10.0).should == 10.0
55
+ FunctionTest.sumif(FunctionTest.a('b1','b3'),'Bear',FunctionTest.a('a1','a2')).should == 100.0
56
+ end
57
+ end
58
+
59
+ describe "sumifs" do
60
+ it "should only sum values that meet all of the criteria" do
61
+ FunctionTest.sumifs(FunctionTest.a('a1','a3'),FunctionTest.a('a1','a3'),10.0,FunctionTest.a('b1','b3'),'Bear').should == 0.0
62
+ FunctionTest.sumifs(FunctionTest.a('a1','a3'),FunctionTest.a('a1','a3'),10.0,FunctionTest.a('b1','b3'),'Pear').should == 10.0
63
+ end
64
+
65
+ it "should work when single cells are given where ranges expected" do
66
+ FunctionTest.sumifs(0.143897265452564, "CAR", "CAR", "FCV", "FCV").should == 0.143897265452564
67
+ end
68
+ end
69
+
70
+ describe "sumproduct" do
71
+ it "should multiply together and then sum the elements in row or column areas given as arguments" do
72
+ FunctionTest.sumproduct(FunctionTest.a('a1','a3'),FunctionTest.a('zx10','zz10')).should == 320.0
73
+ end
74
+ end
75
+
76
+ describe "count" do
77
+ it "should count the number of numeric values in an area" do
78
+ FunctionTest.count(1,"two",FunctionTest.a('a1','a3')).should == 3
79
+ end
80
+ end
81
+
82
+ describe "counta" do
83
+ it "should count the number of numeric or text values in an area" do
84
+ FunctionTest.counta(1,"two",FunctionTest.a('a1','a3')).should == 4
85
+ end
86
+ end
87
+
88
+ describe "average" do
89
+ it "should calculate the mean of its arguments" do
90
+ FunctionTest.average(1,2,3).should == 2
91
+ FunctionTest.average(1,"two",FunctionTest.a('a1','a3')).should == 111.0/3.0
92
+ end
93
+ end
94
+
95
+ describe "subtotal" do
96
+ it "should calculate averages, counts, countas, sums depending on first argument" do
97
+ FunctionTest.subtotal(1.0,1,"two",FunctionTest.a('a1','a3')).should == 111.0/3.0 # Average
98
+ FunctionTest.subtotal(2.0,1,"two",FunctionTest.a('a1','a3')).should == 3 # count
99
+ FunctionTest.subtotal(3.0,1,"two",FunctionTest.a('a1','a3')).should == 4 # counta
100
+ FunctionTest.subtotal(9.0,1,"two",FunctionTest.a('a1','a3')).should == 111 # sum
101
+
102
+ FunctionTest.subtotal(101.0,1,"two",FunctionTest.a('a1','a3')).should == 111.0/3.0 # Average
103
+ FunctionTest.subtotal(102.0,1,"two",FunctionTest.a('a1','a3')).should == 3 # count
104
+ FunctionTest.subtotal(103.0,1,"two",FunctionTest.a('a1','a3')).should == 4 # counta
105
+ FunctionTest.subtotal(109.0,1,"two",FunctionTest.a('a1','a3')).should == 111 # sum
106
+ end
107
+ end
108
+
109
+ describe "match" do
110
+ it "should return the index of the first match of the first argument in the area" do
111
+ FunctionTest.match(10.0,FunctionTest.a('a1','a2'),0.0).should == 1
112
+ FunctionTest.match(100.0,FunctionTest.a('a1','a2'),0.0).should == 2
113
+ FunctionTest.match(1000.0,FunctionTest.a('a1','a2'),0.0).should == :na
114
+ FunctionTest.match('bEAr',FunctionTest.a('b1','b3'),0.0).should == 2
115
+ FunctionTest.match(1000.0,FunctionTest.a('a1','a2'),1.0).should == 2
116
+ FunctionTest.match(1.0,FunctionTest.a('a1','a2'),1.0).should == :na
117
+ FunctionTest.match('Care',FunctionTest.a('b1','b3'),-1.0).should == 1
118
+ FunctionTest.match('Zebra',FunctionTest.a('b1','b3'),-1.0).should == :na
119
+ FunctionTest.match('a',FunctionTest.a('b1','b3'),-1.0).should == 2
120
+ end
121
+ end
122
+
123
+ describe "index" do
124
+ it "should return the value at the row and column number given" do
125
+ FunctionTest.index(FunctionTest.a('a1','b1'),2.0).should == "Pear"
126
+ FunctionTest.index(FunctionTest.a('a1','a3'),2.0).should == 100.0
127
+ FunctionTest.index(FunctionTest.a('b1','b3'),2.0).should == "Bear"
128
+ FunctionTest.index(FunctionTest.a('a1','b3'),1.0,2.0).should == "Pear"
129
+ FunctionTest.index(FunctionTest.a('a1','b3'),2.0,1.0).should == 100.0
130
+ FunctionTest.index(FunctionTest.a('a1','b3'),3.0,1.0).should == 0.0
131
+ FunctionTest.index(FunctionTest.a('a1','b3'),3.0,3.0).should == :ref
132
+ FunctionTest.index(FunctionTest.a('a1','b3'),3.0).should == :ref
133
+ FunctionTest.index(FunctionTest.a('a1','b3'),1.0,0.0).to_a.should == [10.0,"Pear"]
134
+ FunctionTest.index(FunctionTest.a('a1','b3'),0.0,2.0).to_a.should == ["Pear","Bear","Apple"]
135
+ end
136
+ end
137
+
138
+ describe "max" do
139
+ it "should return the argument with the greatest value" do
140
+ FunctionTest.max(1,"two",FunctionTest.a('a1','a3')).should == 100
141
+ end
142
+ end
143
+
144
+ describe "min" do
145
+ it "should return the argument with the smallest value" do
146
+ FunctionTest.min(1000,"two",FunctionTest.a('a1','a3')).should == 10
147
+ end
148
+ end
149
+
150
+ describe "na" do
151
+ it "should return an na error" do
152
+ FunctionTest.na().should == :na
153
+ end
154
+ end
155
+
156
+ describe "iserr" do
157
+ it "should return true if passed a symbol" do
158
+ FunctionTest.iserr(:na).should == true
159
+ FunctionTest.iserr(:ref).should == true
160
+ end
161
+
162
+ it "should return false if passed anything else" do
163
+ FunctionTest.iserr(123).should == false
164
+ end
165
+ end
166
+
167
+ describe "excel_if" do
168
+ it "should return its second argument if its first argument is true" do
169
+ FunctionTest.excel_if(true,:second,:third).should == :second
170
+ end
171
+
172
+ it "should return its third argument if its first argument is false" do
173
+ FunctionTest.excel_if(false,:second,:third).should == :third
174
+ end
175
+ end
176
+
177
+ describe "iferror" do
178
+ it "should return its second value if there is an error in the first" do
179
+ FunctionTest.iferror(FunctionTest.index(FunctionTest.a('a1','b3'),3.0,1.0),"Not found").should == 0.0
180
+ FunctionTest.iferror(FunctionTest.index(FunctionTest.a('a1','b3'),3.0,3.0),"Not found").should == "Not found"
181
+ end
182
+ end
183
+
184
+ describe "excel_and" do
185
+ it "should return true if all its arguments are true" do
186
+ FunctionTest.excel_and(true,true,true) == true
187
+ end
188
+
189
+ it "should return false if any of its arguments are false" do
190
+ FunctionTest.excel_and(true,false,true) == false
191
+ end
192
+ end
193
+
194
+ describe "excel_or" do
195
+ it "should return false if all of its arguments are false" do
196
+ FunctionTest.excel_or(false,false,false) == false
197
+ end
198
+
199
+ it "should return true if any of its arguments are true" do
200
+ FunctionTest.excel_or(false,false,true) == true
201
+ FunctionTest.excel_or(true,true,true) == true
202
+ end
203
+ end
204
+
205
+ describe "left" do
206
+ it "should return the left n characters from a string" do
207
+ FunctionTest.left("ONE").should == "O"
208
+ FunctionTest.left("ONE",1).should == "O"
209
+ FunctionTest.left("ONE",3).should == "ONE"
210
+ end
211
+ end
212
+
213
+ describe "find" do
214
+ it "should find the first occurrence of one string in another" do
215
+ FunctionTest.find("one","onetwothree").should == 1
216
+ FunctionTest.find("one","twoonethree").should == 4
217
+ FunctionTest.find("one","twoonthree").should == :value
218
+ end
219
+
220
+ it "should find the first occurrence of one string in another after a given index" do
221
+ FunctionTest.find("one","onetwothree",1).should == 1
222
+ FunctionTest.find("one","twoonethree",5).should == :value
223
+ FunctionTest.find("one","oneone",2).should == 4
224
+ end
225
+ end
226
+
227
+ describe "ability to respond to empty cell references" do
228
+ it "should return 0 if a reference is made to an empty cell" do
229
+ FunctionTest.a23.should == 0.0
230
+ end
231
+
232
+ it "should return an object that is kind_of?(Empty) if a reference is made to an empty cell" do
233
+ FunctionTest.a23.should be_kind_of(Empty)
234
+ end
235
+ end
236
+
237
+ describe "ability to respond to the a, r and c methods for creating area references" do
238
+ it "should return an Area object for a(start_cell,end_cell)" do
239
+ FunctionTest.a('a1','b10').should be_kind_of(Area)
240
+ end
241
+
242
+ it "should return a Columns object for c(start_column_as_text,end_column_as_text)" do
243
+ FunctionTest.c('a','b').should be_kind_of(Columns)
244
+ end
245
+
246
+ it "should return a Rows object for r(start_row_number,end_row_number)" do
247
+ FunctionTest.r(1,10).should be_kind_of(Rows)
248
+ end
249
+ end
250
+
251
+ class FunctionTest2
252
+ include RubyFromExcel::ExcelFunctions
253
+ def initialize
254
+ @worksheet_names = {'first sheet'=>'sheet1'}
255
+ @workbook_tables = {"FirstTable"=>'Table.new(sheet1,"FirstTable","A1:C3",["ColA", "ColB", "ColC"],1)'}
256
+ end
257
+ def sheet1
258
+ self
259
+ end
260
+ def a1; "Cell A1"; end
261
+ def a2; "Middle A2"; end
262
+ def a3; "Total A3"; end
263
+ def to_s; 'sheet1'; end
264
+ end
265
+
266
+ describe "ability to return a sheet from the full excel name using s('excel name')" do
267
+ it "should return a string for known sheets" do
268
+ sheet = FunctionTest2.new
269
+ sheet.s('first sheet').should == sheet
270
+ end
271
+ end
272
+
273
+ describe "ability to return the table for a given name using t('table name')" do
274
+ it "should return a string for known sheets" do
275
+ FunctionTest2.new.t('FirstTable').reference_for('[#Totals],[ColA]').to_s.should == 'sheet1.a3'
276
+ end
277
+
278
+ it "should return a lowercase form of the class name as variable_name for compatibility with Table" do
279
+ FunctionTest.variable_name.should == "class"
280
+ end
281
+ end
282
+
283
+ describe "indirect method" do
284
+ it "should deal with the simple case where the indirect is pointing to a sheet or cell" do
285
+ FunctionTest2.new.indirect("A$1$").should == "Cell A1"
286
+ end
287
+
288
+ it "should also work with sheet name references" do
289
+ FunctionTest2.new.indirect("'first sheet'!A$1$").should == "Cell A1"
290
+ end
291
+
292
+ it "should also work with table references" do
293
+ FunctionTest2.new.indirect("FirstTable[[#Totals],[ColA]]").should == "Total A3"
294
+ FunctionTest2.new.indirect("FirstTable[[#This Row],[ColA]]",'B2').should == "Middle A2"
295
+ end
296
+
297
+ end
298
+
299
+ describe "boolean values" do
300
+ it "should be possible to treat true values as 1.0 when using them in arithmetic" do
301
+ (true*5.0).should == 5.0
302
+ (true+1.0).should == 2.0
303
+ (true/5.0).should == 0.2
304
+ (true-2.0).should == -1.0
305
+ (5.0*true).should == 5.0
306
+ (1.0+true).should == 2.0
307
+ (5.0/true).should == 5.0
308
+ (2.0-true).should == 1.0
309
+ end
310
+
311
+ it "should be possible to treat false values as 0.0 when using them in arithmetic" do
312
+ (false*5.0).should == 0.0
313
+ (false+1.0).should == 1.0
314
+ (false/5.0).should == 0.0
315
+ (false-2.0).should == -2.0
316
+ (5.0*false).should == 0.0
317
+ (1.0+false).should == 1.0
318
+ (5.0/false).should == (5.0/0.0)
319
+ (2.0-false).should == 2.0
320
+ end
321
+ end
322
+
323
+ describe "symbols" do
324
+ it "should be able to have infix operators applied to them" do
325
+ (-:one).should == :one
326
+ end
327
+
328
+ it "should be able to participate in arithmetic" do
329
+ (1 + :na).should == :na
330
+ (1 - :na).should == :na
331
+ (1 * :na).should == :na
332
+ (1 / :na).should == :na
333
+ (:na + 1).should == :na
334
+ (:na - 1).should == :na
335
+ (:na * 1).should == :na
336
+ (:na / 1).should == :na
337
+ end
338
+ end
339
+
340
+ describe "Strings", "should be able to operate as numbers if they appear to be numbers" do
341
+ it "should be able to have infix operators applied to them" do
342
+ (-"10").should == -10.0
343
+ end
344
+
345
+ it "should be able to participate in arithmetic" do
346
+ (1 + "10").should == 11.0
347
+ (1 - "10").should == -9.0
348
+ (1 * "10").should == 10.0
349
+ (1 / "10").should == 0.1
350
+ ("10" + 1).should == 11.0
351
+ ("10" + "20").should == 30.0
352
+ ("10" + " apples").should == "10 apples"
353
+ ("10" - 1).should == 9.0
354
+ ("10" * 2).should == 20.0
355
+ ("10" / 1).should == 10.0
356
+ end
357
+ end
358
+
359
+ class CacheFuncitonTest
360
+ attr_accessor :method_executed
361
+ include RubyFromExcel::ExcelFunctions
362
+ def a23
363
+ @method_executed = true
364
+ "one"
365
+ end
366
+
367
+ def other_function
368
+ @method_executed = true
369
+ "one"
370
+ end
371
+ end
372
+
373
+ describe "array formula operations" do
374
+
375
+ it "m(expression) should create an ExcelMatrixCollection, carry out an excel_map using the associated block and return an ExcelMatrix" do
376
+ m = FunctionTest.m("one",[[3]],[[:a,:b,:c],[1,2,3]],ExcelMatrix.new([:a,:b,:c])) { |i,j,k| "#{i}#{j}#{k}" }
377
+ m.should be_kind_of(ExcelMatrix)
378
+ m.values.should == [["one3a", "one3b", "one3c"], ["one31", "one32", "one33"]]
379
+ end
380
+
381
+ end