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