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,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'