rubyfromexcel 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. data/README +22 -0
  2. data/bin/rubyfromexcel +20 -0
  3. data/examples/create_and_test_examples.rb +37 -0
  4. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet1.rb +59 -0
  5. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet2.rb +9 -0
  6. data/examples/ruby-versions/array-formulas-ruby/specs/sheet1_rspec.rb +156 -0
  7. data/examples/ruby-versions/array-formulas-ruby/specs/sheet2_rspec.rb +8 -0
  8. data/examples/ruby-versions/array-formulas-ruby/spreadsheet.rb +9 -0
  9. data/examples/ruby-versions/complex-test-ruby/sheets/sheet1.rb +305 -0
  10. data/examples/ruby-versions/complex-test-ruby/sheets/sheet2.rb +147 -0
  11. data/examples/ruby-versions/complex-test-ruby/specs/sheet1_rspec.rb +876 -0
  12. data/examples/ruby-versions/complex-test-ruby/specs/sheet2_rspec.rb +412 -0
  13. data/examples/ruby-versions/complex-test-ruby/spreadsheet.rb +9 -0
  14. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet1.rb +9 -0
  15. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet2.rb +8 -0
  16. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet1_rspec.rb +16 -0
  17. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet2_rspec.rb +16 -0
  18. data/examples/ruby-versions/namedReferenceTest-ruby/spreadsheet.rb +9 -0
  19. data/examples/ruby-versions/pruning-ruby/sheets/sheet1.rb +11 -0
  20. data/examples/ruby-versions/pruning-ruby/sheets/sheet2.rb +14 -0
  21. data/examples/ruby-versions/pruning-ruby/sheets/sheet3.rb +7 -0
  22. data/examples/ruby-versions/pruning-ruby/specs/sheet1_rspec.rb +20 -0
  23. data/examples/ruby-versions/pruning-ruby/specs/sheet2_rspec.rb +20 -0
  24. data/examples/ruby-versions/pruning-ruby/specs/sheet3_rspec.rb +8 -0
  25. data/examples/ruby-versions/pruning-ruby/spreadsheet.rb +9 -0
  26. data/examples/ruby-versions/sharedFormulaTest-ruby/sheets/sheet1.rb +15 -0
  27. data/examples/ruby-versions/sharedFormulaTest-ruby/specs/sheet1_rspec.rb +44 -0
  28. data/examples/ruby-versions/sharedFormulaTest-ruby/spreadsheet.rb +9 -0
  29. data/examples/ruby-versions/table-test-ruby/sheets/sheet1.rb +17 -0
  30. data/examples/ruby-versions/table-test-ruby/sheets/sheet2.rb +5 -0
  31. data/examples/ruby-versions/table-test-ruby/sheets/sheet3.rb +5 -0
  32. data/examples/ruby-versions/table-test-ruby/specs/sheet1_rspec.rb +20 -0
  33. data/examples/ruby-versions/table-test-ruby/specs/sheet2_rspec.rb +8 -0
  34. data/examples/ruby-versions/table-test-ruby/specs/sheet3_rspec.rb +8 -0
  35. data/examples/ruby-versions/table-test-ruby/spreadsheet.rb +9 -0
  36. data/examples/sheets/array-formulas.xlsx +0 -0
  37. data/examples/sheets/complex-test.xlsx +0 -0
  38. data/examples/sheets/namedReferenceTest.xlsx +0 -0
  39. data/examples/sheets/pruning.xlsx +0 -0
  40. data/examples/sheets/sharedFormulaTest.xlsx +0 -0
  41. data/examples/sheets/table-test.xlsx +0 -0
  42. data/examples/sheets/~$array-formulas.xlsx +0 -0
  43. data/examples/unzipped-sheets/array-formulas/[Content_Types].xml +2 -0
  44. data/examples/unzipped-sheets/array-formulas/docProps/app.xml +2 -0
  45. data/examples/unzipped-sheets/array-formulas/docProps/core.xml +2 -0
  46. data/examples/unzipped-sheets/array-formulas/docProps/thumbnail.jpeg +0 -0
  47. data/examples/unzipped-sheets/array-formulas/xl/_rels/workbook.xml.rels +2 -0
  48. data/examples/unzipped-sheets/array-formulas/xl/calcChain.xml +2 -0
  49. data/examples/unzipped-sheets/array-formulas/xl/sharedStrings.xml +2 -0
  50. data/examples/unzipped-sheets/array-formulas/xl/styles.xml +2 -0
  51. data/examples/unzipped-sheets/array-formulas/xl/theme/theme1.xml +2 -0
  52. data/examples/unzipped-sheets/array-formulas/xl/workbook.xml +2 -0
  53. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet1.xml +2 -0
  54. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet2.xml +2 -0
  55. data/examples/unzipped-sheets/complex-test/[Content_Types].xml +2 -0
  56. data/examples/unzipped-sheets/complex-test/docProps/app.xml +2 -0
  57. data/examples/unzipped-sheets/complex-test/docProps/core.xml +2 -0
  58. data/examples/unzipped-sheets/complex-test/xl/_rels/workbook.xml.rels +2 -0
  59. data/examples/unzipped-sheets/complex-test/xl/calcChain.xml +2 -0
  60. data/examples/unzipped-sheets/complex-test/xl/charts/chart1.xml +2 -0
  61. data/examples/unzipped-sheets/complex-test/xl/charts/chart2.xml +2 -0
  62. data/examples/unzipped-sheets/complex-test/xl/comments1.xml +5 -0
  63. data/examples/unzipped-sheets/complex-test/xl/comments2.xml +5 -0
  64. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing1.xml.rels +2 -0
  65. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing2.xml.rels +2 -0
  66. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing1.xml +2 -0
  67. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing2.xml +2 -0
  68. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing1.vml +46 -0
  69. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing2.vml +46 -0
  70. data/examples/unzipped-sheets/complex-test/xl/sharedStrings.xml +2 -0
  71. data/examples/unzipped-sheets/complex-test/xl/styles.xml +2 -0
  72. data/examples/unzipped-sheets/complex-test/xl/theme/theme1.xml +2 -0
  73. data/examples/unzipped-sheets/complex-test/xl/workbook.xml +2 -0
  74. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  75. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet2.xml.rels +2 -0
  76. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet1.xml +2 -0
  77. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet2.xml +2 -0
  78. data/examples/unzipped-sheets/namedReferenceTest/[Content_Types].xml +2 -0
  79. data/examples/unzipped-sheets/namedReferenceTest/docProps/app.xml +2 -0
  80. data/examples/unzipped-sheets/namedReferenceTest/docProps/core.xml +2 -0
  81. data/examples/unzipped-sheets/namedReferenceTest/docProps/thumbnail.jpeg +0 -0
  82. data/examples/unzipped-sheets/namedReferenceTest/xl/_rels/workbook.xml.rels +2 -0
  83. data/examples/unzipped-sheets/namedReferenceTest/xl/calcChain.xml +2 -0
  84. data/examples/unzipped-sheets/namedReferenceTest/xl/styles.xml +2 -0
  85. data/examples/unzipped-sheets/namedReferenceTest/xl/theme/theme1.xml +2 -0
  86. data/examples/unzipped-sheets/namedReferenceTest/xl/workbook.xml +2 -0
  87. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet1.xml +2 -0
  88. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet2.xml +2 -0
  89. data/examples/unzipped-sheets/pruning/[Content_Types].xml +2 -0
  90. data/examples/unzipped-sheets/pruning/docProps/app.xml +2 -0
  91. data/examples/unzipped-sheets/pruning/docProps/core.xml +2 -0
  92. data/examples/unzipped-sheets/pruning/docProps/thumbnail.jpeg +0 -0
  93. data/examples/unzipped-sheets/pruning/xl/_rels/workbook.xml.rels +2 -0
  94. data/examples/unzipped-sheets/pruning/xl/calcChain.xml +2 -0
  95. data/examples/unzipped-sheets/pruning/xl/sharedStrings.xml +2 -0
  96. data/examples/unzipped-sheets/pruning/xl/styles.xml +2 -0
  97. data/examples/unzipped-sheets/pruning/xl/theme/theme1.xml +2 -0
  98. data/examples/unzipped-sheets/pruning/xl/workbook.xml +2 -0
  99. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet1.xml +2 -0
  100. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet2.xml +2 -0
  101. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet3.xml +2 -0
  102. data/examples/unzipped-sheets/sharedFormulaTest/[Content_Types].xml +2 -0
  103. data/examples/unzipped-sheets/sharedFormulaTest/docProps/app.xml +2 -0
  104. data/examples/unzipped-sheets/sharedFormulaTest/docProps/core.xml +2 -0
  105. data/examples/unzipped-sheets/sharedFormulaTest/docProps/thumbnail.jpeg +0 -0
  106. data/examples/unzipped-sheets/sharedFormulaTest/xl/_rels/workbook.xml.rels +2 -0
  107. data/examples/unzipped-sheets/sharedFormulaTest/xl/calcChain.xml +2 -0
  108. data/examples/unzipped-sheets/sharedFormulaTest/xl/styles.xml +2 -0
  109. data/examples/unzipped-sheets/sharedFormulaTest/xl/theme/theme1.xml +2 -0
  110. data/examples/unzipped-sheets/sharedFormulaTest/xl/workbook.xml +2 -0
  111. data/examples/unzipped-sheets/sharedFormulaTest/xl/worksheets/sheet1.xml +2 -0
  112. data/examples/unzipped-sheets/table-test/[Content_Types].xml +2 -0
  113. data/examples/unzipped-sheets/table-test/docProps/app.xml +2 -0
  114. data/examples/unzipped-sheets/table-test/docProps/core.xml +2 -0
  115. data/examples/unzipped-sheets/table-test/xl/_rels/workbook.xml.rels +2 -0
  116. data/examples/unzipped-sheets/table-test/xl/calcChain.xml +2 -0
  117. data/examples/unzipped-sheets/table-test/xl/printerSettings/printerSettings1.bin +0 -0
  118. data/examples/unzipped-sheets/table-test/xl/sharedStrings.xml +2 -0
  119. data/examples/unzipped-sheets/table-test/xl/styles.xml +2 -0
  120. data/examples/unzipped-sheets/table-test/xl/tables/table1.xml +2 -0
  121. data/examples/unzipped-sheets/table-test/xl/theme/theme1.xml +2 -0
  122. data/examples/unzipped-sheets/table-test/xl/workbook.xml +2 -0
  123. data/examples/unzipped-sheets/table-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  124. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet1.xml +2 -0
  125. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet2.xml +2 -0
  126. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet3.xml +2 -0
  127. data/lib/cells/array/array_formula_builder.rb +58 -0
  128. data/lib/cells/array/array_formula_cell.rb +27 -0
  129. data/lib/cells/array/arraying_formula_cell.rb +67 -0
  130. data/lib/cells/array/single_cell_array_formula_builder.rb +9 -0
  131. data/lib/cells/array/single_cell_array_formula_cell.rb +11 -0
  132. data/lib/cells/cell.rb +98 -0
  133. data/lib/cells/cells.rb +9 -0
  134. data/lib/cells/formula/formula_cell.rb +18 -0
  135. data/lib/cells/formula/simple_formula_cell.rb +4 -0
  136. data/lib/cells/shared/shared_formula_builder.rb +15 -0
  137. data/lib/cells/shared/shared_formula_cell.rb +20 -0
  138. data/lib/cells/shared/sharing_formula_cell.rb +36 -0
  139. data/lib/cells/value/value_cell.rb +24 -0
  140. data/lib/excelfile/excelfile.rb +6 -0
  141. data/lib/excelfile/relationships.rb +24 -0
  142. data/lib/excelfile/shared_strings.rb +21 -0
  143. data/lib/excelfile/sheet_names.rb +6 -0
  144. data/lib/excelfile/table.rb +116 -0
  145. data/lib/excelfile/workbook.rb +108 -0
  146. data/lib/excelfile/worksheet.rb +122 -0
  147. data/lib/formulae/compile/formula_builder.rb +316 -0
  148. data/lib/formulae/formulae.rb +6 -0
  149. data/lib/formulae/parse/formula_peg.rb +213 -0
  150. data/lib/formulae/parse/formula_peg.txt +40 -0
  151. data/lib/formulae/run/excel_functions.rb +375 -0
  152. data/lib/formulae/run/excel_matrix.rb +114 -0
  153. data/lib/formulae/run/excel_range.rb +256 -0
  154. data/lib/formulae/run/reference.rb +79 -0
  155. data/lib/optimiser/dependency_builder.rb +86 -0
  156. data/lib/optimiser/optimiser.rb +3 -0
  157. data/lib/optimiser/shared_formula_dependency_builder.rb +43 -0
  158. data/lib/optimiser/workbook_pruner.rb +80 -0
  159. data/lib/rubyfromexcel.rb +105 -0
  160. data/lib/runtime/runtime_formula_builder.rb +32 -0
  161. data/spec/array_formula_builder_spec.rb +35 -0
  162. data/spec/array_formula_cell_spec.rb +17 -0
  163. data/spec/arraying_formula_cell_spec.rb +38 -0
  164. data/spec/dependency_builder_spec.rb +71 -0
  165. data/spec/excel_functions_spec.rb +381 -0
  166. data/spec/excel_matrix_spec.rb +92 -0
  167. data/spec/excel_range_spec.rb +161 -0
  168. data/spec/formula_builder_spec.rb +230 -0
  169. data/spec/formula_peg_spec.rb +165 -0
  170. data/spec/reference_spec.rb +72 -0
  171. data/spec/relationships_spec.rb +51 -0
  172. data/spec/runtime_formula_builder_spec.rb +55 -0
  173. data/spec/shared_formula_builder_spec.rb +29 -0
  174. data/spec/shared_formula_cell_spec.rb +23 -0
  175. data/spec/shared_formula_dependency_builder_spec.rb +48 -0
  176. data/spec/shared_strings_spec.rb +14 -0
  177. data/spec/sharing_formula_cell_spec.rb +79 -0
  178. data/spec/simple_formula_cell_spec.rb +78 -0
  179. data/spec/single_cell_array_formula_builder_spec.rb +19 -0
  180. data/spec/single_cell_array_formula_cell_spec.rb +25 -0
  181. data/spec/spec_helper.rb +2 -0
  182. data/spec/table_spec.rb +100 -0
  183. data/spec/value_cell_spec.rb +49 -0
  184. data/spec/workbook_pruner_spec.rb +27 -0
  185. data/spec/workbook_spec.rb +283 -0
  186. data/spec/worksheet_failiures_spec.rb +41 -0
  187. data/spec/worksheet_spec.rb +486 -0
  188. metadata +291 -0
@@ -0,0 +1,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