rubyfromexcel 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +22 -0
- data/bin/rubyfromexcel +20 -0
- data/examples/create_and_test_examples.rb +37 -0
- data/examples/ruby-versions/array-formulas-ruby/sheets/sheet1.rb +59 -0
- data/examples/ruby-versions/array-formulas-ruby/sheets/sheet2.rb +9 -0
- data/examples/ruby-versions/array-formulas-ruby/specs/sheet1_rspec.rb +156 -0
- data/examples/ruby-versions/array-formulas-ruby/specs/sheet2_rspec.rb +8 -0
- data/examples/ruby-versions/array-formulas-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/complex-test-ruby/sheets/sheet1.rb +305 -0
- data/examples/ruby-versions/complex-test-ruby/sheets/sheet2.rb +147 -0
- data/examples/ruby-versions/complex-test-ruby/specs/sheet1_rspec.rb +876 -0
- data/examples/ruby-versions/complex-test-ruby/specs/sheet2_rspec.rb +412 -0
- data/examples/ruby-versions/complex-test-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet1.rb +9 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet2.rb +8 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet1_rspec.rb +16 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet2_rspec.rb +16 -0
- data/examples/ruby-versions/namedReferenceTest-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/pruning-ruby/sheets/sheet1.rb +11 -0
- data/examples/ruby-versions/pruning-ruby/sheets/sheet2.rb +14 -0
- data/examples/ruby-versions/pruning-ruby/sheets/sheet3.rb +7 -0
- data/examples/ruby-versions/pruning-ruby/specs/sheet1_rspec.rb +20 -0
- data/examples/ruby-versions/pruning-ruby/specs/sheet2_rspec.rb +20 -0
- data/examples/ruby-versions/pruning-ruby/specs/sheet3_rspec.rb +8 -0
- data/examples/ruby-versions/pruning-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/sharedFormulaTest-ruby/sheets/sheet1.rb +15 -0
- data/examples/ruby-versions/sharedFormulaTest-ruby/specs/sheet1_rspec.rb +44 -0
- data/examples/ruby-versions/sharedFormulaTest-ruby/spreadsheet.rb +9 -0
- data/examples/ruby-versions/table-test-ruby/sheets/sheet1.rb +17 -0
- data/examples/ruby-versions/table-test-ruby/sheets/sheet2.rb +5 -0
- data/examples/ruby-versions/table-test-ruby/sheets/sheet3.rb +5 -0
- data/examples/ruby-versions/table-test-ruby/specs/sheet1_rspec.rb +20 -0
- data/examples/ruby-versions/table-test-ruby/specs/sheet2_rspec.rb +8 -0
- data/examples/ruby-versions/table-test-ruby/specs/sheet3_rspec.rb +8 -0
- data/examples/ruby-versions/table-test-ruby/spreadsheet.rb +9 -0
- data/examples/sheets/array-formulas.xlsx +0 -0
- data/examples/sheets/complex-test.xlsx +0 -0
- data/examples/sheets/namedReferenceTest.xlsx +0 -0
- data/examples/sheets/pruning.xlsx +0 -0
- data/examples/sheets/sharedFormulaTest.xlsx +0 -0
- data/examples/sheets/table-test.xlsx +0 -0
- data/examples/sheets/~$array-formulas.xlsx +0 -0
- data/examples/unzipped-sheets/array-formulas/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/array-formulas/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/array-formulas/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/complex-test/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/complex-test/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/complex-test/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/charts/chart1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/charts/chart2.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/comments1.xml +5 -0
- data/examples/unzipped-sheets/complex-test/xl/comments2.xml +5 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing1.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing2.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/drawing1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/drawing2.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing1.vml +46 -0
- data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing2.vml +46 -0
- data/examples/unzipped-sheets/complex-test/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet2.xml.rels +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/pruning/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/pruning/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/pruning/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/pruning/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/pruning/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/pruning/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/pruning/xl/worksheets/sheet3.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/docProps/thumbnail.jpeg +0 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/sharedFormulaTest/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/table-test/[Content_Types].xml +2 -0
- data/examples/unzipped-sheets/table-test/docProps/app.xml +2 -0
- data/examples/unzipped-sheets/table-test/docProps/core.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/_rels/workbook.xml.rels +2 -0
- data/examples/unzipped-sheets/table-test/xl/calcChain.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/printerSettings/printerSettings1.bin +0 -0
- data/examples/unzipped-sheets/table-test/xl/sharedStrings.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/styles.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/tables/table1.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/theme/theme1.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/workbook.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/sheet1.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/sheet2.xml +2 -0
- data/examples/unzipped-sheets/table-test/xl/worksheets/sheet3.xml +2 -0
- data/lib/cells/array/array_formula_builder.rb +58 -0
- data/lib/cells/array/array_formula_cell.rb +27 -0
- data/lib/cells/array/arraying_formula_cell.rb +67 -0
- data/lib/cells/array/single_cell_array_formula_builder.rb +9 -0
- data/lib/cells/array/single_cell_array_formula_cell.rb +11 -0
- data/lib/cells/cell.rb +98 -0
- data/lib/cells/cells.rb +9 -0
- data/lib/cells/formula/formula_cell.rb +18 -0
- data/lib/cells/formula/simple_formula_cell.rb +4 -0
- data/lib/cells/shared/shared_formula_builder.rb +15 -0
- data/lib/cells/shared/shared_formula_cell.rb +20 -0
- data/lib/cells/shared/sharing_formula_cell.rb +36 -0
- data/lib/cells/value/value_cell.rb +24 -0
- data/lib/excelfile/excelfile.rb +6 -0
- data/lib/excelfile/relationships.rb +24 -0
- data/lib/excelfile/shared_strings.rb +21 -0
- data/lib/excelfile/sheet_names.rb +6 -0
- data/lib/excelfile/table.rb +116 -0
- data/lib/excelfile/workbook.rb +108 -0
- data/lib/excelfile/worksheet.rb +122 -0
- data/lib/formulae/compile/formula_builder.rb +316 -0
- data/lib/formulae/formulae.rb +6 -0
- data/lib/formulae/parse/formula_peg.rb +213 -0
- data/lib/formulae/parse/formula_peg.txt +40 -0
- data/lib/formulae/run/excel_functions.rb +375 -0
- data/lib/formulae/run/excel_matrix.rb +114 -0
- data/lib/formulae/run/excel_range.rb +256 -0
- data/lib/formulae/run/reference.rb +79 -0
- data/lib/optimiser/dependency_builder.rb +86 -0
- data/lib/optimiser/optimiser.rb +3 -0
- data/lib/optimiser/shared_formula_dependency_builder.rb +43 -0
- data/lib/optimiser/workbook_pruner.rb +80 -0
- data/lib/rubyfromexcel.rb +105 -0
- data/lib/runtime/runtime_formula_builder.rb +32 -0
- data/spec/array_formula_builder_spec.rb +35 -0
- data/spec/array_formula_cell_spec.rb +17 -0
- data/spec/arraying_formula_cell_spec.rb +38 -0
- data/spec/dependency_builder_spec.rb +71 -0
- data/spec/excel_functions_spec.rb +381 -0
- data/spec/excel_matrix_spec.rb +92 -0
- data/spec/excel_range_spec.rb +161 -0
- data/spec/formula_builder_spec.rb +230 -0
- data/spec/formula_peg_spec.rb +165 -0
- data/spec/reference_spec.rb +72 -0
- data/spec/relationships_spec.rb +51 -0
- data/spec/runtime_formula_builder_spec.rb +55 -0
- data/spec/shared_formula_builder_spec.rb +29 -0
- data/spec/shared_formula_cell_spec.rb +23 -0
- data/spec/shared_formula_dependency_builder_spec.rb +48 -0
- data/spec/shared_strings_spec.rb +14 -0
- data/spec/sharing_formula_cell_spec.rb +79 -0
- data/spec/simple_formula_cell_spec.rb +78 -0
- data/spec/single_cell_array_formula_builder_spec.rb +19 -0
- data/spec/single_cell_array_formula_cell_spec.rb +25 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/table_spec.rb +100 -0
- data/spec/value_cell_spec.rb +49 -0
- data/spec/workbook_pruner_spec.rb +27 -0
- data/spec/workbook_spec.rb +283 -0
- data/spec/worksheet_failiures_spec.rb +41 -0
- data/spec/worksheet_spec.rb +486 -0
- metadata +291 -0
|
@@ -0,0 +1,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
|