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,108 @@
1
+ module RubyFromExcel
2
+ class Workbook
3
+
4
+ attr_reader :relationships
5
+ attr_reader :worksheets
6
+ attr_reader :worksheet_array
7
+ attr_reader :named_references
8
+ attr_accessor :indirects_used
9
+
10
+ def initialize(filename)
11
+ @worksheets = {}
12
+ @worksheet_array = []
13
+ @named_references = {}
14
+ @indirects_used = false
15
+ @relationships = Relationships.for_file(filename)
16
+ xml = File.open(filename) { |f| Nokogiri::XML(f) }.root
17
+ load_shared_strings
18
+ puts "\nLoaded shared strings"
19
+ load_worksheets_from xml
20
+ puts "Loaded #{worksheets.size} worksheets with #{total_cells} cells in total."
21
+ work_out_named_references_from(xml)
22
+ puts "Loaded named references"
23
+ GC.start
24
+ end
25
+
26
+ def load_shared_strings
27
+ return unless relationships.shared_strings
28
+ SharedStrings.instance.load_strings_from_xml(File.open(relationships.shared_strings) { |f| Nokogiri::XML(f) }.root)
29
+ end
30
+
31
+ def load_worksheets_from(xml)
32
+ xml.css("sheet").each do |s|
33
+ worksheet_filename = relationships[s['id']]
34
+ worksheet = Worksheet.from_file(worksheet_filename)
35
+ worksheet.name = File.basename(worksheet_filename,'.xml')
36
+ worksheet.workbook = self
37
+ worksheets[worksheet.name] = worksheet
38
+ SheetNames.instance[s['name']] = worksheet.name
39
+ worksheet_array << worksheet
40
+ puts "Loaded #{worksheet.name} with #{worksheet.cells.size} cells"
41
+ end
42
+ end
43
+
44
+ def work_out_named_references_from(xml)
45
+ xml.css('definedName').each do |defined_name_xml|
46
+ reference_name = defined_name_xml['name'].gsub(/([a-z])([A-Z])/,'\1_\2').downcase.gsub(/[^a-z0-9_]/,'_')
47
+ reference = Formula.parse(defined_name_xml.content).visit(FormulaBuilder.new)
48
+ if defined_name_xml["localSheetId"]
49
+ worksheet_array[defined_name_xml["localSheetId"].to_i].named_references[reference_name] = reference
50
+ else
51
+ named_references[reference_name] = reference
52
+ end
53
+ end
54
+ end
55
+
56
+ def work_out_dependencies
57
+ puts "Working out dependencies..."
58
+ worksheets.each do |name,worksheet|
59
+ puts "Working out dependencies for #{name}"
60
+ worksheet.work_out_dependencies
61
+ end
62
+ puts "Finished working out dependencies"
63
+ end
64
+
65
+ def workbook_pruner
66
+ @workbook_pruner ||= WorkbookPruner.new(self)
67
+ end
68
+
69
+ def prune_cells_not_needed_for_output_sheets(*output_sheets)
70
+ workbook_pruner.prune_cells_not_needed_for_output_sheets(*output_sheets)
71
+ end
72
+
73
+ def convert_cells_to_values_when_independent_of_input_sheets(*input_sheets)
74
+ workbook_pruner.convert_cells_to_values_when_independent_of_input_sheets(*input_sheets)
75
+ end
76
+
77
+ def cell(reference)
78
+ reference =~ /^(sheet\d+)\.([a-z]+\d+)$/
79
+ worksheets[$1].cell($2)
80
+ end
81
+
82
+ def to_ruby(r = RubyScriptWriter.new)
83
+ r.put_coding
84
+ r.puts "require 'rubyfromexcel'"
85
+ r.puts
86
+ r.put_class 'Spreadsheet' do
87
+ r.puts 'include RubyFromExcel::ExcelFunctions'
88
+ r.puts
89
+ if indirects_used
90
+ r.put_method "initialize" do
91
+ r.puts "@worksheet_names = #{Hash[SheetNames.instance.sort]}"
92
+ r.puts "@workbook_tables = #{Hash[Table.tables.sort]}"
93
+ end
94
+ named_references.each do |name,reference|
95
+ r.put_simple_method name, reference
96
+ end
97
+ end
98
+ end
99
+ r.puts 'Dir[File.join(File.dirname(__FILE__),"sheets/","sheet*.rb")].each {|f| Spreadsheet.autoload(File.basename(f,".rb").capitalize,f)}'
100
+ r.to_s
101
+ end
102
+
103
+ def total_cells
104
+ worksheets.inject(0) { |total,a| a.last.cells.size + total }
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,122 @@
1
+ module RubyFromExcel
2
+ class Worksheet
3
+
4
+ def self.from_file(filename)
5
+ xml = File.open(filename) { |f| Nokogiri::XML(f).root }
6
+ worksheet = Worksheet.new(xml)
7
+ relationships = Relationships.for_file(filename)
8
+ xml.css('tablePart').each do |table_reference_xml|
9
+ table_xml = File.open(relationships[table_reference_xml['id']]) {|f| Nokogiri::XML(f).root }
10
+ Table.from_xml(worksheet,table_xml)
11
+ end
12
+ worksheet
13
+ end
14
+
15
+ attr_accessor :cells
16
+ attr_accessor :name
17
+ attr_reader :named_references
18
+ attr_accessor :workbook
19
+
20
+ def initialize(xml)
21
+ self.cells = {}
22
+ @named_references = {}
23
+ load_cells_from xml
24
+ GC.start
25
+ end
26
+
27
+ def load_cells_from(xml)
28
+ xml.css("c").each do |cell_xml|
29
+ new_cell = create_cell_from cell_xml
30
+ next unless new_cell
31
+ self.cells[new_cell.reference.to_s] = new_cell
32
+ end
33
+ xml = nil
34
+ let_cells_alter_other_cells_if_required
35
+ end
36
+
37
+ def create_cell_from(xml)
38
+ formula = xml.at_css("f")
39
+ if formula
40
+ formula_type = formula['t']
41
+ ref = formula['ref']
42
+ return SimpleFormulaCell.new(self,xml) unless formula_type
43
+ return SharingFormulaCell.new(self,xml) if formula_type == 'shared' && ref
44
+ return SharedFormulaCell.new(self,xml) if formula_type == 'shared'
45
+ return ArrayingFormulaCell.new(self,xml) if formula_type == 'array' && ref =~ /:/
46
+ return SingleCellArrayFormulaCell.new(self,xml) if formula_type == 'array'
47
+ end
48
+ return ValueCell.new(self,xml) if xml.at_css("v")
49
+ nil
50
+ end
51
+
52
+ def let_cells_alter_other_cells_if_required
53
+ cells.each do |reference,cell|
54
+ cell.alter_other_cells_if_required
55
+ end
56
+ end
57
+
58
+ def work_out_dependencies
59
+ cells.each do |reference,cell|
60
+ cell.work_out_dependencies
61
+ end
62
+ end
63
+
64
+ def cell(reference)
65
+ cells[reference]
66
+ end
67
+
68
+ def replace_cell(reference,new_cell)
69
+ cells[reference] = new_cell
70
+ end
71
+
72
+ def to_ruby(r = RubyScriptWriter.new)
73
+ r.put_coding
74
+ r.comment SheetNames.instance.key(variable_name)
75
+ r.put_class class_name, 'Spreadsheet' do
76
+ cells.each do |reference,cell|
77
+ begin
78
+ cell.to_ruby(r)
79
+ rescue Exception => e
80
+ puts "Error in #{cell.inspect}"
81
+ raise
82
+ end
83
+ end
84
+ if workbook.indirects_used
85
+ named_references.each do |name,reference|
86
+ r.put_simple_method name, reference
87
+ end
88
+ end
89
+ end
90
+ r.to_s
91
+ end
92
+
93
+ def to_test(r = RubySpecWriter.new)
94
+ r.put_coding
95
+ r.puts "require_relative '../spreadsheet'"
96
+ r.comment SheetNames.instance.key(variable_name)
97
+ r.put_description "'#{class_name}'" do
98
+ r.put_simple_method variable_name, "$spreadsheet ||= Spreadsheet.new; $spreadsheet.#{variable_name}"
99
+ r.puts
100
+ cells.each do |reference,cell|
101
+ cell.to_test(r)
102
+ end
103
+ end
104
+ r.to_s
105
+ end
106
+
107
+ def class_name
108
+ name.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
109
+ end
110
+
111
+ def variable_name
112
+ name.gsub(/([a-z])([A-Z])/,'\1_\2').downcase.gsub(/[^a-z0-9_]/,'_')
113
+ end
114
+
115
+ alias to_s variable_name
116
+
117
+ def worksheet_file_name
118
+ variable_name
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,316 @@
1
+ module TerminalNode
2
+ def to_method_name
3
+ self.gsub(/([a-z])([A-Z])/,'\1_\2').downcase.gsub(/[^a-z0-9_]/,'_')
4
+ end
5
+ end
6
+
7
+ module RubyFromExcel
8
+ class ExcelFunctionNotImplementedError < Exception
9
+ end
10
+
11
+ class DependsOnCalculatedFormulaError < Exception
12
+ end
13
+
14
+ class FunctionCompiler
15
+ include RubyFromExcel::ExcelFunctions
16
+ attr_accessor :worksheet
17
+
18
+ def initialize(worksheet)
19
+ self.worksheet = worksheet
20
+ end
21
+
22
+ def method_missing(method,*arguments, &block)
23
+ return super unless arguments.empty?
24
+ return super unless block == nil
25
+ return find_or_create_worksheet(method.to_s) if method.to_s =~ /sheet\d+/
26
+ return super unless method.to_s =~ /[a-z]+\d+/
27
+ cell = worksheet.cell(method.to_s)
28
+ return 0.0.extend(Empty) unless cell
29
+ raise DependsOnCalculatedFormulaError.new unless cell.can_be_replaced_with_value?
30
+ cell.value_for_including
31
+ end
32
+
33
+ def find_or_create_worksheet(worksheet_name)
34
+ @worksheets ||= {}
35
+ return @worksheets[worksheet_name] if @worksheets.has_key?(worksheet_name)
36
+ new_worksheet = FunctionCompiler.new(worksheet.workbooks.worksheets[worksheet_name])
37
+ @worksheets[worksheet_name] = new_worksheet
38
+ new_worksheet
39
+ end
40
+
41
+ def to_s
42
+ worksheet.to_s
43
+ end
44
+
45
+ end
46
+
47
+ class FormulaBuilder
48
+
49
+ attr_accessor :formula_cell
50
+
51
+ def initialize(formula_cell = nil)
52
+ self.formula_cell = formula_cell
53
+ end
54
+
55
+ def formula(*expressions)
56
+ expressions.map { |e| e.visit(self) }.join
57
+ end
58
+
59
+ def number(number_as_text)
60
+ number_as_text.to_f
61
+ end
62
+
63
+ def percentage(percentage_as_text)
64
+ (percentage_as_text.to_f/100).to_s
65
+ end
66
+
67
+ def brackets(*expression)
68
+ "(#{expression.map{ |e| e.visit(self)}.join})"
69
+ end
70
+
71
+ def named_reference(name, worksheet = formula_cell.worksheet)
72
+ worksheet.named_references[name.to_method_name] ||
73
+ worksheet.workbook.named_references[name.to_method_name] ||
74
+ ":name"
75
+ end
76
+
77
+ def cell(reference)
78
+ Reference.new(reference).to_ruby
79
+ end
80
+
81
+ def area(start_area,end_area)
82
+ "a('#{cell(start_area)}','#{cell(end_area)}')"
83
+ end
84
+
85
+ def column_range(start_area,end_area)
86
+ "c('#{cell(start_area)}','#{cell(end_area)}')"
87
+ end
88
+
89
+ def row_range(start_area,end_area)
90
+ "r(#{cell(start_area)},#{cell(end_area)})"
91
+ end
92
+
93
+ def sheet_reference(sheet_name,reference)
94
+ sheet_name = $1 if sheet_name.to_s =~ /^(\d+)\.0+$/
95
+ if reference.type == :named_reference
96
+ worksheet = formula_cell.worksheet.workbook.worksheets[SheetNames.instance[sheet_name]]
97
+ raise Exception.new("#{sheet_name.inspect} not found in #{SheetNames.instance} and therefore in #{formula_cell.worksheet.workbook.worksheets.keys}") unless worksheet
98
+ named_reference(reference.first,worksheet)
99
+ else
100
+ "#{SheetNames.instance[sheet_name]}.#{reference.visit(self)}"
101
+ end
102
+ end
103
+
104
+ def table_reference(table_name,structured_reference)
105
+ Table.reference_for(table_name,structured_reference,formula_cell && formula_cell.reference).to_s
106
+ end
107
+
108
+ def local_table_reference(structured_reference)
109
+ Table.reference_for_local_reference(formula_cell,structured_reference).to_s
110
+ end
111
+
112
+ alias :quoted_sheet_reference :sheet_reference
113
+
114
+ OPERATOR_CONVERSIONS = { '^' => '**' }
115
+
116
+ def operator(excel_operator)
117
+ OPERATOR_CONVERSIONS[excel_operator] || excel_operator
118
+ end
119
+
120
+ def string_join(*strings)
121
+ strings.map { |s| s.type == :string ? s.visit(self) : "#{s.visit(self)}.to_s"}.join('+')
122
+ end
123
+
124
+ def string(string_text)
125
+ string_text.inspect
126
+ end
127
+
128
+ def function(name,*args)
129
+ raise ExcelFunctionNotImplementedError.new("#{name}(#{args})") unless self.respond_to?("#{name.downcase}_function")
130
+ self.send("#{name.downcase}_function",*args)
131
+ end
132
+
133
+ def self.excel_function(name,name_to_use_in_ruby = name)
134
+ define_method("#{name}_function") do |*args|
135
+ standard_function name_to_use_in_ruby, args
136
+ end
137
+ end
138
+
139
+ excel_function :and, :excel_and
140
+ excel_function :average
141
+ excel_function :count
142
+ excel_function :counta
143
+ excel_function :choose
144
+ excel_function :abs
145
+ excel_function :find
146
+ excel_function :if, :excel_if
147
+ excel_function :iferror
148
+ excel_function :iserr
149
+ excel_function :left
150
+ excel_function :max
151
+ excel_function :min
152
+ excel_function :na
153
+ excel_function :not, '!'
154
+ excel_function :or, :excel_or
155
+ excel_function :sum
156
+ excel_function :sumif
157
+ excel_function :sumifs
158
+ excel_function :subtotal
159
+ excel_function :sumproduct
160
+
161
+ def standard_function(name_to_use_in_ruby,args)
162
+ "#{name_to_use_in_ruby}(#{args.map {|a| a.visit(self) }.join(',')})"
163
+ end
164
+
165
+ def index_function(*args)
166
+ attempt_to_caclulate_index(*args) ||
167
+ standard_function("index",args)
168
+ end
169
+
170
+ def match_function(*args)
171
+ attempt_to_caclulate_match(*args) ||
172
+ standard_function("match",args)
173
+ end
174
+
175
+ def attempt_to_caclulate_index(lookup_array,row_number,column_number = :ignore)
176
+ lookup_array = range_for(lookup_array)
177
+ row_number = single_value_for(row_number)
178
+ column_number = single_value_for(column_number) unless column_number == :ignore
179
+ return nil unless lookup_array
180
+ return nil unless row_number
181
+ return nil unless column_number
182
+ if column_number == :ignore
183
+ ref = FunctionCompiler.new(formula_cell.worksheet).calculate_index_formula(lookup_array,row_number,nil,:index_reference)
184
+ else
185
+ ref = FunctionCompiler.new(formula_cell.worksheet).calculate_index_formula(lookup_array,row_number,column_number,:index_reference)
186
+ end
187
+ return nil unless ref
188
+ return nil if ref.is_a?(Symbol)
189
+ return ref.to_ruby(true)
190
+ rescue DependsOnCalculatedFormulaError
191
+ return nil
192
+ end
193
+
194
+ def attempt_to_caclulate_match(lookup_value,lookup_array,match_type = :ignore)
195
+ lookup_value = single_value_for(lookup_value)
196
+ lookup_array = range_for(lookup_array)
197
+ match_type = single_value_for(match_type) unless match_type == :ignore
198
+ return nil unless lookup_value
199
+ return nil unless lookup_array
200
+ return nil unless match_type
201
+ result = nil
202
+ if match_type == :ignore
203
+ result = FunctionCompiler.new(formula_cell.worksheet).match(lookup_value,lookup_array).to_f
204
+ else
205
+ result = FunctionCompiler.new(formula_cell.worksheet).match(lookup_value,lookup_array,match_type)
206
+ end
207
+ result.respond_to?(:to_f) ? result.to_f : result
208
+ rescue DependsOnCalculatedFormulaError
209
+ return nil
210
+ end
211
+
212
+ def single_value_for(ast)
213
+ return nil unless ast.respond_to?(:visit)
214
+ ast = ast.visit(self)
215
+ return ast if ast == "true"
216
+ return ast if ast == "false"
217
+ return ast if ast.is_a?(Numeric)
218
+ return ast if ast =~ /^[0-9.]+$/
219
+ return $1 if ast =~ /^"([^"]*)"$/
220
+ return nil unless formula_cell
221
+ return nil unless formula_cell.worksheet
222
+ return nil unless ast =~ /^(sheet\d+)?\.?([a-z]+\d+)$/
223
+ cell = $1 ? formula_cell.worksheet.workbook.worksheets[$1].cell($2) : formula_cell.worksheet.cell($2)
224
+ return nil unless cell
225
+ return nil unless cell.can_be_replaced_with_value?
226
+ cell.value_for_including
227
+ end
228
+
229
+ def range_for(ast)
230
+ return nil unless ast.respond_to?(:visit)
231
+ return nil unless formula_cell
232
+ return nil unless formula_cell.worksheet
233
+ ast = ast.visit(self)
234
+ return nil unless ast =~ /^(sheet\d+)?\.?a\('([a-z]+\d+)','([a-z]+\d+)'\)$/
235
+ worksheet = $1 ? formula_cell.worksheet.workbook.worksheets[$1] : formula_cell.worksheet
236
+ FunctionCompiler.new(worksheet).a($2,$3)
237
+ end
238
+
239
+ def indirect_function(text_formula)
240
+ attempt_to_parse_indirect(text_formula) || (formula_cell.worksheet.workbook.indirects_used = true; "indirect(#{text_formula.visit(self)},'#{formula_cell && formula_cell.reference}')")
241
+ end
242
+
243
+ def attempt_to_parse_indirect(text_formula)
244
+ return parse_and_visit(text_formula.first) if text_formula.type == :string
245
+ return nil unless text_formula.type == :string_join
246
+ reformated_indirect = text_formula.map do |non_terminal|
247
+ if non_terminal.respond_to?(:type)
248
+ case non_terminal.type
249
+ when :string, :number
250
+ non_terminal
251
+ when :cell
252
+ cell = formula_cell.worksheet.cell(non_terminal.visit(self))
253
+ if cell
254
+ return nil unless cell.can_be_replaced_with_value?
255
+ cell.value_for_including
256
+ else
257
+ ""
258
+ end
259
+ when :sheet_reference, :named_reference, :table_reference, :local_table_reference
260
+ reference = non_terminal.visit(self)
261
+ return nil unless reference =~ /^(sheet\d+)\.([a-z]+\d+)$/
262
+ cell = formula_cell.worksheet.workbook.worksheets[$1].cell($2)
263
+ if cell
264
+ return nil unless cell.can_be_replaced_with_value?
265
+ cell.value_for_including
266
+ else
267
+ ""
268
+ end
269
+ else
270
+ return nil
271
+ end
272
+ else
273
+ non_terminal
274
+ end
275
+ end
276
+ parse_and_visit(reformated_indirect.join)
277
+ end
278
+
279
+ def parse_and_visit(text)
280
+ ast = Formula.parse(text)
281
+ return ":name" unless ast
282
+ ast.visit(self.class.new(formula_cell))
283
+ end
284
+
285
+ def comparison(*strings)
286
+ strings.map { |s| s.visit(self) }.join
287
+ end
288
+
289
+ def arithmetic(*strings)
290
+ strings.map { |s| s.visit(self) }.join
291
+ end
292
+
293
+ def prefix(prefix,thing)
294
+ "#{prefix.visit(self)}#{thing.visit(self)}"
295
+ end
296
+
297
+ COMPARATOR_CONVERSIONS = {'=' => '==' }
298
+
299
+ def comparator(string)
300
+ COMPARATOR_CONVERSIONS[string] || string
301
+ end
302
+
303
+ def boolean_true
304
+ "true"
305
+ end
306
+
307
+ def boolean_false
308
+ "false"
309
+ end
310
+
311
+ def null
312
+ 0.0
313
+ end
314
+
315
+ end
316
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'run/reference'
2
+ require_relative 'run/excel_range'
3
+ require_relative 'run/excel_matrix'
4
+ require_relative 'run/excel_functions'
5
+ require_relative 'compile/formula_builder'
6
+ require_relative 'parse/formula_peg'