rubyfromexcel 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|