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,114 @@
|
|
1
|
+
module RubyFromExcel
|
2
|
+
class ExcelMatrixCollection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :matrices, :max_rows, :max_columns
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
self.matrices = args.map do |a|
|
9
|
+
a.respond_to?(:to_excel_matrix) ? a.to_excel_matrix : ExcelMatrix.new(a)
|
10
|
+
end
|
11
|
+
coerce_to_equal_sizes
|
12
|
+
end
|
13
|
+
|
14
|
+
def coerce_to_equal_sizes
|
15
|
+
self.max_rows = matrices.max_by(&:rows).rows
|
16
|
+
self.max_columns = matrices.max_by(&:columns).columns
|
17
|
+
matrices.each do |m|
|
18
|
+
m.add_columns!(max_columns - m.columns)
|
19
|
+
m.add_rows!(max_rows - m.rows)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def each &block
|
24
|
+
block ? to_enum.each { |e| yield e } : to_enum
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_enum
|
28
|
+
Enumerator.new do |yielder|
|
29
|
+
0.upto(max_rows-1) do |j|
|
30
|
+
0.upto(max_columns-1) do |i|
|
31
|
+
yielder << matrices.map { |m| m.values[j][i] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def matrix_map
|
38
|
+
results = Array.new(max_rows) { Array.new(max_columns) }
|
39
|
+
0.upto(max_rows-1) do |j|
|
40
|
+
0.upto(max_columns-1) do |i|
|
41
|
+
results[j][i] = yield *matrices.map { |m| m.values[j][i] }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
em = ExcelMatrix.new(results)
|
45
|
+
if em.rows == 1 && em.columns == 1
|
46
|
+
return em.values.first.first
|
47
|
+
else
|
48
|
+
return em
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class ExcelMatrix < ExcelRange
|
56
|
+
include Enumerable
|
57
|
+
|
58
|
+
attr_accessor :values
|
59
|
+
|
60
|
+
def initialize(values)
|
61
|
+
@values = values.is_a?(Array) ? (values.first.is_a?(Array) ? values : [values] ) : [[values]]
|
62
|
+
end
|
63
|
+
|
64
|
+
# origin 0,0
|
65
|
+
def array_formula_offset(row_index,column_index)
|
66
|
+
row_index = 0 if values.size == 1
|
67
|
+
column_index = 0 if values[row_index].size == 1
|
68
|
+
values[row_index][column_index] || :na
|
69
|
+
end
|
70
|
+
|
71
|
+
def rows
|
72
|
+
return 0 unless values
|
73
|
+
values.size
|
74
|
+
end
|
75
|
+
|
76
|
+
def columns
|
77
|
+
return 0 unless values && values.first
|
78
|
+
values.first.size
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_rows!(number = 1)
|
82
|
+
return if number < 1
|
83
|
+
number.times { self.values << self.values.first }
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_columns!(number = 1)
|
87
|
+
return if number < 1
|
88
|
+
number.times do
|
89
|
+
values.each do |row|
|
90
|
+
row << row.last
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def each &block
|
96
|
+
block ? to_enum.each { |e| yield e } : to_enum
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_enum
|
100
|
+
Enumerator.new do |yielder|
|
101
|
+
values.each do |row|
|
102
|
+
row.each do |value|
|
103
|
+
yielder << value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_excel_matrix
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
module RubyFromExcel
|
2
|
+
|
3
|
+
class ExcelRange
|
4
|
+
end
|
5
|
+
|
6
|
+
class ExcelRangeCommon < ExcelRange
|
7
|
+
include Enumerable
|
8
|
+
attr_accessor :sheet
|
9
|
+
|
10
|
+
def each &block
|
11
|
+
block ? to_enum.each { |e| yield e } : to_enum
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def defined_cells(regexp = /^[a-z]{1,3}\d+$/io)
|
17
|
+
sheet.methods.find_all {|m| m =~ regexp }
|
18
|
+
end
|
19
|
+
|
20
|
+
def value_at(reference)
|
21
|
+
sheet.send(reference.to_s)
|
22
|
+
rescue ZeroDivisionError => e
|
23
|
+
return :div0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Area < ExcelRangeCommon
|
28
|
+
attr_accessor :start_cell, :end_cell
|
29
|
+
|
30
|
+
def initialize(sheet, start_cell,end_cell)
|
31
|
+
self.sheet = sheet
|
32
|
+
self.start_cell = Reference.new(start_cell)
|
33
|
+
self.end_cell = Reference.new(end_cell)
|
34
|
+
end
|
35
|
+
|
36
|
+
def row(index) # index = 0 for top row
|
37
|
+
row_number = row_number_for_index(index)
|
38
|
+
Area.new(sheet,"#{start_cell.column}#{row_number}","#{end_cell.column}#{row_number}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def rows(start_index, end_index)
|
42
|
+
start_row_number = row_number_for_index(start_index)
|
43
|
+
end_row_number = row_number_for_index(end_index)
|
44
|
+
Area.new(sheet,"#{start_cell.column}#{start_row_number}","#{end_cell.column}#{end_row_number}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def column(index) # index = 0 for first column
|
48
|
+
column_number = start_cell.column_number + index
|
49
|
+
Area.new(sheet,Reference.ruby_for(column_number,start_cell.row_number),Reference.ruby_for(column_number,end_cell.row_number))
|
50
|
+
end
|
51
|
+
|
52
|
+
def index(row_number,column_number) #origin at 1,1
|
53
|
+
reference = index_reference(row_number,column_number)
|
54
|
+
return reference if reference.is_a?(Symbol)
|
55
|
+
value_at(reference.to_ruby)
|
56
|
+
end
|
57
|
+
|
58
|
+
def index_reference(row_number,column_number)
|
59
|
+
ref = array_formula_index(row_number-1, column_number-1)
|
60
|
+
return ref if ref.is_a?(Symbol)
|
61
|
+
ref.worksheet = sheet
|
62
|
+
ref
|
63
|
+
end
|
64
|
+
|
65
|
+
def array_formula_offset(row_index,column_index)
|
66
|
+
value_at array_formula_index(row_index,column_index).to_ruby
|
67
|
+
end
|
68
|
+
|
69
|
+
def array_formula_index(row_index,column_index)
|
70
|
+
column_index = 0 if single_column?
|
71
|
+
row_index = 0 if single_row?
|
72
|
+
reference = start_cell.shift([row_index,column_index])
|
73
|
+
return :ref unless include?(reference)
|
74
|
+
reference
|
75
|
+
end
|
76
|
+
|
77
|
+
def each(&block)
|
78
|
+
return @array.each(&block) if @array
|
79
|
+
start_cell.row_number.upto(end_cell.row_number) do |row|
|
80
|
+
start_cell.column_number.upto(end_cell.column_number) do |column_number|
|
81
|
+
yield value_at(Reference.ruby_for(column_number,row))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# def to_enum
|
87
|
+
# to_reference_enum.map {|reference| value_at reference}
|
88
|
+
# end
|
89
|
+
|
90
|
+
def to_a
|
91
|
+
@array ||= self.map {|v| v }
|
92
|
+
end
|
93
|
+
|
94
|
+
def dependencies
|
95
|
+
@dependencies ||=
|
96
|
+
(start_cell.row_number..end_cell.row_number).map do |row|
|
97
|
+
(start_cell.column_number..end_cell.column_number).map do |column_number|
|
98
|
+
Reference.ruby_for(column_number,row,sheet)
|
99
|
+
end
|
100
|
+
end.flatten
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_reference_enum
|
104
|
+
Enumerator.new do |yielder|
|
105
|
+
start_cell.row_number.upto(end_cell.row_number) do |row|
|
106
|
+
start_cell.column_number.upto(end_cell.column_number) do |column_number|
|
107
|
+
yielder << Reference.ruby_for(column_number,row)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_excel_matrix
|
114
|
+
ExcelMatrix.new(to_grid)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_grid
|
118
|
+
(start_cell.row_number..end_cell.row_number).map do |row|
|
119
|
+
(start_cell.column_number..end_cell.column_number).map do |column_number|
|
120
|
+
value_at(Reference.ruby_for(column_number,row))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def single_row_or_column?
|
126
|
+
single_row? || single_column?
|
127
|
+
end
|
128
|
+
|
129
|
+
def single_column?
|
130
|
+
start_cell.column == end_cell.column
|
131
|
+
end
|
132
|
+
|
133
|
+
def single_row?
|
134
|
+
start_cell.row_number == end_cell.row_number
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
"#{sheet}.a('#{start_cell.to_ruby}','#{end_cell.to_ruby}')"
|
139
|
+
end
|
140
|
+
|
141
|
+
def include_cell?(cell)
|
142
|
+
return false unless cell.worksheet == sheet
|
143
|
+
include?(cell.reference)
|
144
|
+
end
|
145
|
+
|
146
|
+
def row_number_for_index(index)
|
147
|
+
return (end_cell.row_number + 1 + index) if index < 0
|
148
|
+
row_number = start_cell.row_number + index
|
149
|
+
end
|
150
|
+
|
151
|
+
def include?(reference)
|
152
|
+
return false unless reference.column_number >= start_cell.column_number
|
153
|
+
return false unless reference.column_number <= end_cell.column_number
|
154
|
+
return false unless reference.row_number >= start_cell.row_number
|
155
|
+
return false unless reference.row_number <= end_cell.row_number
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
def method_missing(method,*args,&block)
|
160
|
+
super unless start_cell.to_s == end_cell.to_s
|
161
|
+
super unless value_at(start_cell).respond_to?(method)
|
162
|
+
value_at(start_cell).send(method,*args,&block)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
class Columns < ExcelRangeCommon
|
168
|
+
|
169
|
+
attr_accessor :start_column_number, :end_column_number
|
170
|
+
|
171
|
+
def initialize(sheet,start_column,end_column)
|
172
|
+
self.sheet = sheet
|
173
|
+
self.start_column_number = Reference.column_to_integer(start_column)
|
174
|
+
self.end_column_number = Reference.column_to_integer(end_column)
|
175
|
+
end
|
176
|
+
|
177
|
+
def array_formula_index(row_index,column_index)
|
178
|
+
column_index = 0 if single_column?
|
179
|
+
reference = Reference.new(Reference.ruby_for(start_column_number+column_index,row_index+1))
|
180
|
+
return :na unless include?(reference)
|
181
|
+
reference.to_ruby
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_enum
|
185
|
+
Enumerator.new do |yielder|
|
186
|
+
(start_column_number..end_column_number).each do |column_number|
|
187
|
+
(1..maximum_row_in_column_number(column_number)).each do |row|
|
188
|
+
yielder << value_at(Reference.ruby_for(column_number,row))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def include?(reference)
|
197
|
+
return false unless reference.column_number >= start_column_number
|
198
|
+
return false unless reference.column_number <= end_column_number
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
def maximum_row_in_column_number(column_number)
|
203
|
+
cells_in_column = defined_cells(/^#{Reference.integer_to_column(column_number)}\d+$/i)
|
204
|
+
cells_in_column.empty? ? 0 : cells_in_column.last[/\d+/i].to_i
|
205
|
+
end
|
206
|
+
|
207
|
+
def single_column?
|
208
|
+
start_column_number == end_column_number
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
class Rows < ExcelRangeCommon
|
213
|
+
attr_accessor :start_row_number, :end_row_number
|
214
|
+
|
215
|
+
def initialize(sheet,start_row_number,end_row_number)
|
216
|
+
self.sheet = sheet
|
217
|
+
self.start_row_number = start_row_number.to_i
|
218
|
+
self.end_row_number = end_row_number.to_i
|
219
|
+
end
|
220
|
+
|
221
|
+
def array_formula_index(row_index,column_index)
|
222
|
+
row_index = 0 if single_row?
|
223
|
+
reference = Reference.new(Reference.ruby_for(column_index+1,row_index+1))
|
224
|
+
return :na unless include?(reference)
|
225
|
+
reference.to_ruby
|
226
|
+
end
|
227
|
+
|
228
|
+
def to_enum
|
229
|
+
Enumerator.new do |yielder|
|
230
|
+
(start_row_number..end_row_number).each do |row|
|
231
|
+
(1..maximum_column_number_in_row_number(row)).each do |column_number|
|
232
|
+
yielder << value_at(Reference.ruby_for(column_number,row))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def maximum_column_number_in_row_number(row_number)
|
241
|
+
cells_in_row = defined_cells(/^[a-z]{1,3}#{row_number}$/i)
|
242
|
+
cells_in_row.empty? ? 0 : Reference.column_to_integer(cells_in_row.last[/[a-z]+/i])
|
243
|
+
end
|
244
|
+
|
245
|
+
def single_row?
|
246
|
+
start_row_number == end_row_number
|
247
|
+
end
|
248
|
+
|
249
|
+
def include?(reference)
|
250
|
+
return false unless reference.row_number >= start_row_number
|
251
|
+
return false unless reference.row_number <= end_row_number
|
252
|
+
true
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module RubyFromExcel
|
2
|
+
class Reference
|
3
|
+
attr_accessor :row, :column, :worksheet
|
4
|
+
attr_accessor :absolute_row, :absolute_column
|
5
|
+
|
6
|
+
def self.ruby_for(column_number,row_number,worksheet = nil)
|
7
|
+
worksheet ? "#{worksheet}.#{integer_to_column(column_number)}#{row_number.to_i}" : "#{integer_to_column(column_number)}#{row_number.to_i}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.column_to_integer(string)
|
11
|
+
@column_to_integer_cache ||= {}
|
12
|
+
return @column_to_integer_cache[string] ||= string.downcase.each_byte.to_a.reverse.each.with_index.inject(0) do |memo,byte_with_index|
|
13
|
+
memo = memo + ((byte_with_index.first - 96) * (26**byte_with_index.last))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.integer_to_column(integer)
|
18
|
+
@integer_to_column_cache ||= {}
|
19
|
+
return @integer_to_column_cache[integer] ||= self.calculate_integer_to_column(integer)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.calculate_integer_to_column(integer)
|
23
|
+
raise Exception.new("Column numbering starts at 1, received #{integer}") unless integer >= 1
|
24
|
+
text = (integer-1).to_i.to_s(26)
|
25
|
+
(text[0...-1].tr('1-9a-z','abcdefghijklmnopqrstuvwxyz')+text[-1,1].tr('0-9a-z','abcdefghijklmnopqrstuvwxyz')).gsub('a0','z').gsub(/([b-z])0/) { $1.tr('b-z','a-y')+"z" }
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(reference_as_text, worksheet = nil)
|
29
|
+
@worksheet = worksheet
|
30
|
+
reference_as_text.downcase!
|
31
|
+
@row = reference_as_text[/\d+/]
|
32
|
+
@column = reference_as_text[/[a-z]+/]
|
33
|
+
@absolute_row = (reference_as_text =~ /\$\d/)
|
34
|
+
@absolute_column = (reference_as_text =~ /\$[a-z]/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def shift(offset_array)
|
38
|
+
self.dup.shift!(offset_array)
|
39
|
+
end
|
40
|
+
|
41
|
+
def shift!(offset_array)
|
42
|
+
return self unless offset_array && offset_array.size == 2
|
43
|
+
shift_row_by! offset_array.first
|
44
|
+
shift_column_by! offset_array.last
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_ruby(include_worksheet = false)
|
49
|
+
include_worksheet ? "#{worksheet}.#{column}#{row && row.to_i}".downcase : "#{column}#{row && row.to_i}".downcase
|
50
|
+
end
|
51
|
+
|
52
|
+
alias to_s to_ruby
|
53
|
+
|
54
|
+
def row_number
|
55
|
+
row.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def column_number
|
59
|
+
Reference.column_to_integer(column)
|
60
|
+
end
|
61
|
+
|
62
|
+
def -(other_reference)
|
63
|
+
[row_number - other_reference.row_number, column_number - other_reference.column_number]
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def shift_row_by!(offset)
|
69
|
+
return if absolute_row
|
70
|
+
self.row = (row_number + offset).to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def shift_column_by!(offset)
|
74
|
+
return if absolute_column
|
75
|
+
self.column = Reference.integer_to_column(column_number + offset)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RubyFromExcel
|
2
|
+
class DependencyBuilder
|
3
|
+
attr_accessor :formula_cell, :dependencies, :worksheet
|
4
|
+
|
5
|
+
def initialize(formula_cell = nil)
|
6
|
+
self.formula_cell = formula_cell
|
7
|
+
self.worksheet = formula_cell.worksheet
|
8
|
+
end
|
9
|
+
|
10
|
+
def formula(*expressions)
|
11
|
+
self.dependencies = []
|
12
|
+
expressions.each do |e|
|
13
|
+
e.visit(self)
|
14
|
+
end
|
15
|
+
self.dependencies.uniq.sort
|
16
|
+
end
|
17
|
+
|
18
|
+
def sheet_reference(sheet_name,reference)
|
19
|
+
sheet_name = $1 if sheet_name.to_s =~ /^(\d+)\.0+$/
|
20
|
+
using_worksheet(SheetNames.instance[sheet_name]) do
|
21
|
+
reference.visit(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias quoted_sheet_reference sheet_reference
|
26
|
+
|
27
|
+
def named_reference(name)
|
28
|
+
dependencies_for reference_for_name(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def dependencies_for(full_reference)
|
32
|
+
return [] unless full_reference =~ /(sheet\d+)\.(.*)/
|
33
|
+
sheet_name, reference = $1, $2
|
34
|
+
using_worksheet(sheet_name) do
|
35
|
+
case reference
|
36
|
+
when /a\('(.*?)','(.*?)'\)/; area($1,$2)
|
37
|
+
else; self.dependencies << full_reference
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def table_reference(table_name,structured_reference)
|
43
|
+
dependencies_for Table.reference_for(table_name,structured_reference,formula_cell && formula_cell.reference).to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def local_table_reference(structured_reference)
|
47
|
+
dependencies_for Table.reference_for_local_reference(formula_cell,structured_reference).to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def cell(reference)
|
51
|
+
self.dependencies << Reference.new(reference,worksheet).to_ruby(true)
|
52
|
+
Reference.new(reference).to_ruby
|
53
|
+
end
|
54
|
+
|
55
|
+
def area(start_area,end_area)
|
56
|
+
self.dependencies.concat Area.new(worksheet,Reference.new(start_area).to_ruby,Reference.new(end_area).to_ruby).dependencies
|
57
|
+
end
|
58
|
+
|
59
|
+
def function(name,*args)
|
60
|
+
if name == "INDIRECT"
|
61
|
+
args.first.visit(self)
|
62
|
+
dependencies_for FormulaBuilder.new(formula_cell).indirect_function(args.first)
|
63
|
+
else
|
64
|
+
args.each { |a| a.visit(self) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def reference_for_name(name)
|
69
|
+
worksheet.named_references[name.to_method_name] ||
|
70
|
+
workbook.named_references[name.to_method_name] ||
|
71
|
+
(raise Exception.new("#{name} not found"))
|
72
|
+
end
|
73
|
+
|
74
|
+
def workbook
|
75
|
+
formula_cell.worksheet.workbook
|
76
|
+
end
|
77
|
+
|
78
|
+
def using_worksheet(sheet_name)
|
79
|
+
original_worksheet = self.worksheet
|
80
|
+
self.worksheet = workbook.worksheets[sheet_name] || (raise Exception.new("#{sheet_name} not found"))
|
81
|
+
yield
|
82
|
+
self.worksheet = original_worksheet
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RubyFromExcel
|
2
|
+
|
3
|
+
class SharedFormulaDependencyBuilder < DependencyBuilder
|
4
|
+
attr_accessor :shared_formula_offset
|
5
|
+
|
6
|
+
def initialize(formula_cell = nil, shared_formula_offset = nil)
|
7
|
+
super formula_cell
|
8
|
+
self.shared_formula_offset = shared_formula_offset
|
9
|
+
end
|
10
|
+
|
11
|
+
def cell(reference)
|
12
|
+
self.dependencies << Reference.new(reference,worksheet).shift!(shared_formula_offset).to_ruby(true)
|
13
|
+
Reference.new(reference).to_ruby
|
14
|
+
end
|
15
|
+
|
16
|
+
def function(name,*args)
|
17
|
+
if name == "INDIRECT"
|
18
|
+
args.first.visit(self)
|
19
|
+
dependencies_for SharedFormulaBuilder.new(formula_cell,shared_formula_offset).indirect_function(args.first)
|
20
|
+
else
|
21
|
+
args.each { |a| a.visit(self) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias area_without_offset area
|
26
|
+
|
27
|
+
def area(start_area,end_area)
|
28
|
+
self.dependencies.concat Area.new(worksheet,Reference.new(start_area).shift!(shared_formula_offset).to_ruby,Reference.new(end_area).shift!(shared_formula_offset).to_ruby).dependencies
|
29
|
+
end
|
30
|
+
|
31
|
+
def dependencies_for(full_reference)
|
32
|
+
return [] unless full_reference =~ /(sheet\d+)\.(.*)/
|
33
|
+
sheet_name, reference = $1, $2
|
34
|
+
using_worksheet(sheet_name) do
|
35
|
+
case reference
|
36
|
+
when /a\('(.*?)','(.*?)'\)/; area_without_offset($1,$2)
|
37
|
+
else; self.dependencies << full_reference
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RubyFromExcel
|
2
|
+
|
3
|
+
class WorkbookPruner
|
4
|
+
attr_reader :workbook, :cells_to_keep, :output_sheet_names
|
5
|
+
|
6
|
+
def initialize(workbook)
|
7
|
+
@workbook = workbook
|
8
|
+
end
|
9
|
+
|
10
|
+
def prune_cells_not_needed_for_output_sheets(*output_sheet_names)
|
11
|
+
@cells_to_keep = {}
|
12
|
+
workbook.work_out_dependencies
|
13
|
+
@output_sheet_names = output_sheet_names
|
14
|
+
find_dependencies_of output_sheet_names
|
15
|
+
delete_cells_that_we_dont_want_to_keep
|
16
|
+
cells_to_keep = nil # So that we can garbage collect
|
17
|
+
GC.start
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_dependencies_of(output_sheet_names)
|
21
|
+
output_sheet_names.each do |output_sheet_name|
|
22
|
+
puts "Cascading dependencies for #{output_sheet_name}"
|
23
|
+
output_sheet = workbook.worksheets[SheetNames.instance[output_sheet_name]]
|
24
|
+
raise Exception.new("#{output_sheet_name} not found #{SheetNames.instance.inspect} #{workbook.worksheets.keys.inspect}") unless output_sheet
|
25
|
+
output_sheet.cells.each do |reference,cell|
|
26
|
+
keep_dependencies_for(cell)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
puts "#{cells_to_keep.size} cells kept, #{workbook.total_cells - cells_to_keep.size} pruned"
|
30
|
+
end
|
31
|
+
|
32
|
+
def keep_dependencies_for(cell)
|
33
|
+
return unless cell
|
34
|
+
return if cells_to_keep.has_key?(cell.to_s)
|
35
|
+
cells_to_keep[cell.to_s] = cell
|
36
|
+
cell.dependencies.each do |reference|
|
37
|
+
keep_dependencies_for workbook.cell(reference)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_cells_that_we_dont_want_to_keep
|
42
|
+
workbook.worksheets.each do |name,sheet|
|
43
|
+
sheet.cells.delete_if do |reference,cell|
|
44
|
+
!cells_to_keep.has_key?(cell.to_s)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def convert_cells_to_values_when_independent_of_input_sheets(*input_sheet_names)
|
50
|
+
input_sheet_names = input_sheet_names.map { |name| SheetNames.instance[name] }
|
51
|
+
count = 0
|
52
|
+
workbook.worksheets.each do |name,sheet|
|
53
|
+
puts "Converting cells into values in #{name}"
|
54
|
+
sheet.cells.each do |reference,cell|
|
55
|
+
next if cell.is_a?(ValueCell)
|
56
|
+
unless depends_on_input_sheets?(cell,input_sheet_names)
|
57
|
+
# puts "converting #{cell}"
|
58
|
+
count = count + 1
|
59
|
+
sheet.replace_cell(reference,ValueCell.for(cell))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
puts "#{count} formula cells replaced with their values"
|
64
|
+
GC.start
|
65
|
+
prune_cells_not_needed_for_output_sheets(*output_sheet_names)
|
66
|
+
end
|
67
|
+
|
68
|
+
def depends_on_input_sheets?(cell,input_sheet_names,stack_level = 0)
|
69
|
+
return true if stack_level > 100
|
70
|
+
return false unless cell
|
71
|
+
return true if input_sheet_names.include?(cell.worksheet.variable_name)
|
72
|
+
return false unless cell.dependencies
|
73
|
+
return true if cell.dependencies.any? do |reference|
|
74
|
+
depends_on_input_sheets?(workbook.cell(reference),input_sheet_names,stack_level + 1)
|
75
|
+
end
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|