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,213 @@
|
|
1
|
+
require 'rubypeg'
|
2
|
+
|
3
|
+
class Formula < RubyPeg
|
4
|
+
|
5
|
+
def root
|
6
|
+
formula
|
7
|
+
end
|
8
|
+
|
9
|
+
def formula
|
10
|
+
node :formula do
|
11
|
+
optional { space } && one_or_more { expression }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def expression
|
16
|
+
string_join || arithmetic || comparison || thing
|
17
|
+
end
|
18
|
+
|
19
|
+
def thing
|
20
|
+
function || brackets || any_reference || string || percentage || number || boolean || prefix || named_reference
|
21
|
+
end
|
22
|
+
|
23
|
+
def argument
|
24
|
+
expression || null
|
25
|
+
end
|
26
|
+
|
27
|
+
def function
|
28
|
+
node :function do
|
29
|
+
terminal(/[A-Z]+/) && ignore { terminal("(") } && space && optional { argument } && any_number_of { (space && ignore { terminal(",") } && space && argument) } && space && ignore { terminal(")") }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def brackets
|
34
|
+
node :brackets do
|
35
|
+
ignore { terminal("(") } && space && one_or_more { expression } && space && ignore { terminal(")") }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def string_join
|
40
|
+
node :string_join do
|
41
|
+
thing && one_or_more { (space && ignore { terminal("&") } && space && thing) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def arithmetic
|
46
|
+
node :arithmetic do
|
47
|
+
thing && one_or_more { (space && operator && space && thing) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def comparison
|
52
|
+
node :comparison do
|
53
|
+
thing && space && comparator && space && thing
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def comparator
|
58
|
+
node :comparator do
|
59
|
+
terminal(">=") || terminal("<=") || terminal("<>") || terminal(">") || terminal("<") || terminal("=")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def string
|
64
|
+
node :string do
|
65
|
+
ignore { terminal("\"") } && terminal(/[^"]*/) && ignore { terminal("\"") }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def any_reference
|
70
|
+
table_reference || local_table_reference || quoted_sheet_reference || sheet_reference || sheetless_reference
|
71
|
+
end
|
72
|
+
|
73
|
+
def percentage
|
74
|
+
node :percentage do
|
75
|
+
terminal(/[-+]?[0-9]+\.?[0-9]*/) && ignore { terminal("%") }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def number
|
80
|
+
node :number do
|
81
|
+
terminal(/[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def operator
|
86
|
+
node :operator do
|
87
|
+
terminal("+") || terminal("-") || terminal("/") || terminal("*") || terminal("^")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def table_reference
|
92
|
+
node :table_reference do
|
93
|
+
table_name && ignore { terminal("[") } && (complex_structured_reference || simple_structured_reference) && ignore { terminal("]") }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def local_table_reference
|
98
|
+
node :local_table_reference do
|
99
|
+
ignore { terminal("[") } && (complex_structured_reference || overly_structured_reference || simple_structured_reference) && ignore { terminal("]") }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def table_name
|
104
|
+
terminal(/[.a-zA-Z0-9]+/)
|
105
|
+
end
|
106
|
+
|
107
|
+
def complex_structured_reference
|
108
|
+
terminal(/\[[^\u005d]*\],\[[^\u005d]*\]/)
|
109
|
+
end
|
110
|
+
|
111
|
+
def overly_structured_reference
|
112
|
+
ignore { terminal("[") } && simple_structured_reference && ignore { terminal("]") }
|
113
|
+
end
|
114
|
+
|
115
|
+
def simple_structured_reference
|
116
|
+
terminal(/[^\u005d]*/)
|
117
|
+
end
|
118
|
+
|
119
|
+
def named_reference
|
120
|
+
node :named_reference do
|
121
|
+
terminal(/[a-zA-Z][\w_.]+/)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def quoted_sheet_reference
|
126
|
+
node :quoted_sheet_reference do
|
127
|
+
single_quoted_string && ignore { terminal("!") } && (sheetless_reference || named_reference)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def sheet_reference
|
132
|
+
node :sheet_reference do
|
133
|
+
terminal(/[a-zA-Z0-9][\w_.]+/) && ignore { terminal("!") } && (sheetless_reference || named_reference)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def single_quoted_string
|
138
|
+
ignore { terminal("'") } && terminal(/[^']*/) && ignore { terminal("'") }
|
139
|
+
end
|
140
|
+
|
141
|
+
def sheetless_reference
|
142
|
+
column_range || row_range || area || cell
|
143
|
+
end
|
144
|
+
|
145
|
+
def column_range
|
146
|
+
node :column_range do
|
147
|
+
column && ignore { terminal(":") } && column
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def row_range
|
152
|
+
node :row_range do
|
153
|
+
row && ignore { terminal(":") } && row
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def area
|
158
|
+
node :area do
|
159
|
+
reference && ignore { terminal(":") } && reference
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def cell
|
164
|
+
node :cell do
|
165
|
+
reference
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def row
|
170
|
+
terminal(/\$?\d+/)
|
171
|
+
end
|
172
|
+
|
173
|
+
def column
|
174
|
+
terminal(/\$?[A-Z]+/)
|
175
|
+
end
|
176
|
+
|
177
|
+
def reference
|
178
|
+
terminal(/\$?[A-Z]+\$?[0-9]+/)
|
179
|
+
end
|
180
|
+
|
181
|
+
def boolean
|
182
|
+
boolean_true || boolean_false
|
183
|
+
end
|
184
|
+
|
185
|
+
def boolean_true
|
186
|
+
node :boolean_true do
|
187
|
+
ignore { terminal("TRUE") }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def boolean_false
|
192
|
+
node :boolean_false do
|
193
|
+
ignore { terminal("FALSE") }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def prefix
|
198
|
+
node :prefix do
|
199
|
+
terminal(/[-+]/) && thing
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def space
|
204
|
+
ignore { terminal(/[ \n]*/) }
|
205
|
+
end
|
206
|
+
|
207
|
+
def null
|
208
|
+
node :null do
|
209
|
+
followed_by { terminal(",") }
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
formula := space? expression+
|
2
|
+
expression = string_join | arithmetic | comparison | thing
|
3
|
+
thing = function | brackets | any_reference | string | percentage | number | boolean | prefix | named_reference
|
4
|
+
argument = expression | null
|
5
|
+
function := /[A-Z]+/ `'(' space argument? (space `',' space argument)* space `')'
|
6
|
+
brackets := `'(' space expression+ space `')'
|
7
|
+
string_join := thing (space `"&" space thing)+
|
8
|
+
arithmetic := thing (space operator space thing)+
|
9
|
+
comparison := thing space comparator space thing
|
10
|
+
comparator := '>=' | '<=' | '<>' | '>' | '<' | '='
|
11
|
+
string := `'"' /[^"]*/ `'"'
|
12
|
+
any_reference = table_reference | local_table_reference | quoted_sheet_reference | sheet_reference | sheetless_reference
|
13
|
+
percentage := /[-+]?[0-9]+\.?[0-9]*/ `'%'
|
14
|
+
number := /[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/
|
15
|
+
operator := '+' | '-' | '/' | '*' | '^'
|
16
|
+
table_reference := table_name `'[' (complex_structured_reference | simple_structured_reference) `']'
|
17
|
+
local_table_reference := `'[' (complex_structured_reference | overly_structured_reference | simple_structured_reference) `']'
|
18
|
+
table_name = /[.a-zA-Z0-9]+/
|
19
|
+
complex_structured_reference = /\[[^\u005d]*\],\[[^\u005d]*\]/
|
20
|
+
overly_structured_reference = `'[' simple_structured_reference `']'
|
21
|
+
simple_structured_reference = /[^\u005d]*/
|
22
|
+
named_reference := /[a-zA-Z][\w_.]+/
|
23
|
+
quoted_sheet_reference := single_quoted_string `'!' (sheetless_reference | named_reference)
|
24
|
+
sheet_reference := /[a-zA-Z0-9][\w_.]+/ `'!' (sheetless_reference | named_reference)
|
25
|
+
single_quoted_string = `"'" /[^']*/ `"'"
|
26
|
+
sheetless_reference = column_range | row_range | area | cell
|
27
|
+
column_range := column `':' column
|
28
|
+
row_range := row `':' row
|
29
|
+
area := reference `':' reference
|
30
|
+
cell := reference
|
31
|
+
row = /\$?\d+/
|
32
|
+
column = /\$?[A-Z]+/
|
33
|
+
reference = /\$?[A-Z]+\$?[0-9]+/
|
34
|
+
boolean = boolean_true | boolean_false
|
35
|
+
boolean_true := `'TRUE'
|
36
|
+
boolean_false := `'FALSE'
|
37
|
+
prefix := /[-+]/ thing
|
38
|
+
space = `/[ \n]*/
|
39
|
+
null := &','
|
40
|
+
|
@@ -0,0 +1,375 @@
|
|
1
|
+
class Numeric
|
2
|
+
def number_like?; true; end
|
3
|
+
end
|
4
|
+
|
5
|
+
class String
|
6
|
+
def +@; number_like? ? self.to_f : self; end
|
7
|
+
def -@; number_like? ? -self.to_f : self; end
|
8
|
+
alias old_plus +
|
9
|
+
alias old_multiply *
|
10
|
+
def +(other) (other.number_like? && number_like?) ? self.to_f + other : self.old_plus(other) end
|
11
|
+
def -(other) (other.number_like? && number_like?) ? self.to_f - other : :value end
|
12
|
+
def *(other) (other.number_like? && number_like?) ? self.to_f * other : self.old_multiply(other) end
|
13
|
+
def /(other) (other.number_like? && number_like?) ? self.to_f / other : :value end
|
14
|
+
def coerce(other); number_like? ? self.to_f.coerce(other) : [other,:value]; end
|
15
|
+
def number_like?
|
16
|
+
self =~ /^\d+\.?\d*$/
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Symbol
|
21
|
+
def +@; self; end
|
22
|
+
def -@; self; end
|
23
|
+
def coerce(other)
|
24
|
+
return [other,self] if other.is_a?(Symbol)
|
25
|
+
[self,self]
|
26
|
+
end
|
27
|
+
def +(other) self end
|
28
|
+
def -(other) self end
|
29
|
+
def *(other) self end
|
30
|
+
def /(other) self end
|
31
|
+
def **(other) self end
|
32
|
+
def to_ruby(include_worksheet = false); self; end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
class TrueClass
|
37
|
+
def +(other); 1.0 + other; end
|
38
|
+
def -(other); 1.0 - other; end
|
39
|
+
def *(other); 1.0 * other; end
|
40
|
+
def /(other); 1.0 / other; end
|
41
|
+
def coerce(other); 1.0.coerce(other); end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FalseClass
|
45
|
+
def +(other); 0.0 + other; end
|
46
|
+
def -(other); 0.0 - other; end
|
47
|
+
def *(other); 0.0 * other; end
|
48
|
+
def /(other); 0.0 / other; end
|
49
|
+
def coerce(other); 0.0.coerce(other); end
|
50
|
+
end
|
51
|
+
|
52
|
+
module RubyFromExcel
|
53
|
+
|
54
|
+
module Empty
|
55
|
+
# Used to indicate empty cells
|
56
|
+
end
|
57
|
+
|
58
|
+
module ExcelFunctions
|
59
|
+
|
60
|
+
def result_cache
|
61
|
+
@result_cache ||= {}
|
62
|
+
end
|
63
|
+
|
64
|
+
def formula_cache
|
65
|
+
@formula_cache ||= {}
|
66
|
+
end
|
67
|
+
|
68
|
+
def recalculate
|
69
|
+
formula_cache.clear
|
70
|
+
result_cache.clear
|
71
|
+
end
|
72
|
+
|
73
|
+
def set(cell,value)
|
74
|
+
instance_variable_name = "@#{cell}"
|
75
|
+
unless instance_variable_defined?(instance_variable_name)
|
76
|
+
self.class.class_eval do
|
77
|
+
if method_defined?(cell)
|
78
|
+
alias_method "old_#{cell}", cell
|
79
|
+
define_method(cell) do
|
80
|
+
instance_variable_get(instance_variable_name) || self.send("old_#{cell}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
instance_variable_set(instance_variable_name, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def method_missing(method,*arguments, &block)
|
89
|
+
return super unless arguments.empty?
|
90
|
+
return super unless block == nil
|
91
|
+
return find_or_create_worksheet(method.to_s) if method.to_s =~ /sheet\d+/
|
92
|
+
return super unless method.to_s =~ /[a-z]+\d+/
|
93
|
+
0.0.extend(Empty)
|
94
|
+
end
|
95
|
+
|
96
|
+
def find_or_create_worksheet(worksheet_name)
|
97
|
+
@worksheets ||= {variable_name => self}
|
98
|
+
return @worksheets[worksheet_name] if @worksheets.has_key?(worksheet_name)
|
99
|
+
worksheet = self.class.class_eval("#{worksheet_name.capitalize}.new")
|
100
|
+
worksheet.instance_variable_set("@worksheets",@worksheets)
|
101
|
+
worksheet.instance_variable_set("@formula_cache",formula_cache)
|
102
|
+
@worksheets[worksheet_name] = worksheet
|
103
|
+
worksheet
|
104
|
+
end
|
105
|
+
|
106
|
+
def sum(*args)
|
107
|
+
flatten_and_inject(args) do |counter,arg|
|
108
|
+
arg.is_a?(Numeric) ? counter + arg.to_f : counter
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def sumif(check_range,criteria,sum_range = check_range)
|
113
|
+
sumifs(sum_range,check_range,criteria)
|
114
|
+
end
|
115
|
+
|
116
|
+
def sumifs(sum_range,*args)
|
117
|
+
return :na if iserr(sum_range)
|
118
|
+
return :na if args.any? { |c| iserr(c) }
|
119
|
+
if sum_range.is_a?(ExcelRange)
|
120
|
+
return :na unless sum_range.single_row_or_column?
|
121
|
+
sum_range = sum_range.to_a
|
122
|
+
else
|
123
|
+
sum_range = [sum_range]
|
124
|
+
end
|
125
|
+
checks = Hash[*args].to_a.map do |check|
|
126
|
+
check_range, check_value = check.first, check.last
|
127
|
+
if check_range.is_a?(ExcelRange)
|
128
|
+
return :na unless check_range.single_row_or_column?
|
129
|
+
check_range = check_range.to_a
|
130
|
+
else
|
131
|
+
check_range = [check_range]
|
132
|
+
end
|
133
|
+
check_range = check_range.map { |c| c.to_s.downcase }
|
134
|
+
check_value = check_value.to_s.downcase
|
135
|
+
[check_range,check_value]
|
136
|
+
end
|
137
|
+
accumulator = 0
|
138
|
+
sum_range.each_with_index do |potential_sum,i|
|
139
|
+
next unless checks.all? { |c| c.first[i] == c.last }
|
140
|
+
next unless potential_sum.is_a?(Numeric)
|
141
|
+
accumulator = accumulator + potential_sum
|
142
|
+
end
|
143
|
+
accumulator
|
144
|
+
end
|
145
|
+
|
146
|
+
def sumproduct(*ranges)
|
147
|
+
ranges.map do |range|
|
148
|
+
return :na unless range.respond_to?(:to_a)
|
149
|
+
range.to_a
|
150
|
+
end.transpose.map do |values|
|
151
|
+
values.inject(1) do |cell,total|
|
152
|
+
total * cell
|
153
|
+
end
|
154
|
+
end.inject(0) do |product,total|
|
155
|
+
total + product
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def choose(choice,*choices)
|
160
|
+
return choice if iserr(choice)
|
161
|
+
choices[choice - 1]
|
162
|
+
end
|
163
|
+
|
164
|
+
def count(*args)
|
165
|
+
flatten_and_inject(args) do |counter,arg|
|
166
|
+
arg.is_a?(Numeric) && !arg.is_a?(Empty) ? counter + 1 : counter
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def counta(*args)
|
171
|
+
flatten_and_inject(args) do |counter,arg|
|
172
|
+
arg.is_a?(Empty) ? counter : counter + 1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def max(*args)
|
177
|
+
args = args.map { |arg| arg.respond_to?(:to_a) ? arg.to_a : arg }
|
178
|
+
args.flatten!
|
179
|
+
args.delete_if { |arg| !arg.kind_of?(Numeric) }
|
180
|
+
args.delete_if { |arg| arg.kind_of?(Empty) }
|
181
|
+
return error if error = args.find { |arg| iserr(arg) }
|
182
|
+
args.max
|
183
|
+
end
|
184
|
+
|
185
|
+
def abs(value)
|
186
|
+
return value if iserr(value)
|
187
|
+
return :value unless value.respond_to?(:abs)
|
188
|
+
value.abs
|
189
|
+
end
|
190
|
+
|
191
|
+
def min(*args)
|
192
|
+
args = args.map { |arg| arg.respond_to?(:to_a) ? arg.to_a : arg }
|
193
|
+
args.flatten!
|
194
|
+
args.delete_if { |arg| !arg.kind_of?(Numeric) }
|
195
|
+
args.delete_if { |arg| arg.kind_of?(Empty) }
|
196
|
+
return error if error = args.find { |arg| iserr(arg) }
|
197
|
+
args.min
|
198
|
+
end
|
199
|
+
|
200
|
+
def flatten_and_inject(args,&block)
|
201
|
+
args = args.map do |arg|
|
202
|
+
return arg if iserr(arg)
|
203
|
+
arg.respond_to?(:to_a) ? arg.to_a : arg
|
204
|
+
end
|
205
|
+
args.flatten!
|
206
|
+
args.inject(0,&block)
|
207
|
+
end
|
208
|
+
|
209
|
+
def left(string,characters = 1)
|
210
|
+
return string if iserr(string)
|
211
|
+
return characters if iserr(characters)
|
212
|
+
string.slice(0,characters)
|
213
|
+
end
|
214
|
+
|
215
|
+
def find(string_to_find,string_to_search,start_index = 1)
|
216
|
+
return string_to_find if iserr(string_to_find)
|
217
|
+
return string_to_search if iserr(string_to_search)
|
218
|
+
return start_index if iserr(start_index)
|
219
|
+
result = string_to_search.index(string_to_find,start_index - 1 )
|
220
|
+
result ? result + 1 : :value
|
221
|
+
end
|
222
|
+
|
223
|
+
def average(*args)
|
224
|
+
total = sum(*args)
|
225
|
+
number = count(*args)
|
226
|
+
return total if iserr(total)
|
227
|
+
return number if iserr(number)
|
228
|
+
total / number
|
229
|
+
end
|
230
|
+
|
231
|
+
def subtotal(type,*args)
|
232
|
+
case type
|
233
|
+
when 1.0, 101.0; average(*args)
|
234
|
+
when 2.0, 102.0; count(*args)
|
235
|
+
when 3.0, 103.0; counta(*args)
|
236
|
+
when 9.0, 109.0; sum(*args)
|
237
|
+
else
|
238
|
+
raise Exception.new("subtotal type #{type} on #{args} not implemented")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def match(lookup_value,lookup_array,match_type = 1.0)
|
243
|
+
formula_cache[[:match,lookup_value,lookup_array,match_type]] ||= calculate_match(lookup_value,lookup_array,match_type)
|
244
|
+
end
|
245
|
+
|
246
|
+
def calculate_match(lookup_value,lookup_array,match_type = 1.0)
|
247
|
+
return lookup_value if iserr(lookup_value)
|
248
|
+
return lookup_array if iserr(lookup_array)
|
249
|
+
return match_type if iserr(match_type)
|
250
|
+
lookup_value = lookup_value.downcase if lookup_value.respond_to?(:downcase)
|
251
|
+
case match_type
|
252
|
+
when 0, 0.0, false
|
253
|
+
lookup_array.each_with_index do |item,index|
|
254
|
+
item = item.downcase if item.respond_to?(:downcase)
|
255
|
+
return index+1 if lookup_value == item
|
256
|
+
end
|
257
|
+
return :na
|
258
|
+
when 1, 1.0, true
|
259
|
+
lookup_array.each_with_index do |item, index|
|
260
|
+
next if lookup_value.is_a?(String) && !item.is_a?(String)
|
261
|
+
next if lookup_value.is_a?(Numeric) && !item.is_a?(Numeric)
|
262
|
+
item = item.downcase if item.respond_to?(:downcase)
|
263
|
+
if item > lookup_value
|
264
|
+
return :na if index == 0
|
265
|
+
return index
|
266
|
+
end
|
267
|
+
end
|
268
|
+
return lookup_array.to_a.size
|
269
|
+
when -1, -1.0
|
270
|
+
lookup_array.each_with_index do |item, index|
|
271
|
+
next if lookup_value.is_a?(String) && !item.is_a?(String)
|
272
|
+
next if lookup_value.is_a?(Numeric) && !item.is_a?(Numeric)
|
273
|
+
item = item.downcase if item.respond_to?(:downcase)
|
274
|
+
if item < lookup_value
|
275
|
+
return :na if index == 0
|
276
|
+
return index
|
277
|
+
end
|
278
|
+
end
|
279
|
+
return lookup_array.to_a.size - 1
|
280
|
+
end
|
281
|
+
return :na
|
282
|
+
end
|
283
|
+
|
284
|
+
def index(area,row_number, column_number = nil)
|
285
|
+
formula_cache[[:index,area,row_number,column_number]] ||= calculate_index_formula(area,row_number, column_number)
|
286
|
+
end
|
287
|
+
|
288
|
+
def calculate_index_formula(area,row_number, column_number = nil, method = :index)
|
289
|
+
return area if iserr(area)
|
290
|
+
return row_number if iserr(row_number)
|
291
|
+
if area.single_row?
|
292
|
+
return area.send(method,row_number,column_number) if column_number
|
293
|
+
return area.send(method,1,row_number)
|
294
|
+
elsif area.single_column?
|
295
|
+
return area.send(method,row_number,column_number) if column_number
|
296
|
+
return area.send(method,row_number,1)
|
297
|
+
else
|
298
|
+
return :ref unless row_number && column_number
|
299
|
+
return area.column(column_number-1) if row_number == 0.0
|
300
|
+
return area.row(row_number-1) if column_number == 0.0
|
301
|
+
return area.send(method,row_number,column_number)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def na(*arg)
|
306
|
+
:na
|
307
|
+
end
|
308
|
+
|
309
|
+
def ref(*arg)
|
310
|
+
:ref
|
311
|
+
end
|
312
|
+
|
313
|
+
def iserr(arg)
|
314
|
+
arg.is_a? Symbol
|
315
|
+
end
|
316
|
+
|
317
|
+
def excel_if(condition,true_value,false_value)
|
318
|
+
condition ? true_value : false_value
|
319
|
+
end
|
320
|
+
|
321
|
+
def iferror(value,value_if_error)
|
322
|
+
iserr(value) ? value_if_error : value
|
323
|
+
rescue ZeroDivisionError => e
|
324
|
+
puts e
|
325
|
+
return :div0
|
326
|
+
end
|
327
|
+
|
328
|
+
def excel_and(*args)
|
329
|
+
args.all? {|a| a == true }
|
330
|
+
end
|
331
|
+
|
332
|
+
def excel_or(*args)
|
333
|
+
args.any? { |a| a == true }
|
334
|
+
end
|
335
|
+
|
336
|
+
def a(start_cell,end_cell)
|
337
|
+
result_cache[[:a,start_cell,end_cell]] ||= Area.new(self,start_cell,end_cell)
|
338
|
+
end
|
339
|
+
|
340
|
+
def r(start_row,end_row)
|
341
|
+
result_cache[[:r,start_row,end_row]] ||= Rows.new(self,start_row,end_row)
|
342
|
+
end
|
343
|
+
|
344
|
+
def c(start_column_number,end_column_number)
|
345
|
+
result_cache[[:r,start_column_number,end_column_number]] ||= Columns.new(self,start_column_number,end_column_number)
|
346
|
+
end
|
347
|
+
|
348
|
+
def s(full_name_of_worksheet)
|
349
|
+
full_name_of_worksheet = $1 if full_name_of_worksheet.to_s =~ /^(\d+)\.0+$/
|
350
|
+
self.send(@worksheet_names[full_name_of_worksheet])
|
351
|
+
end
|
352
|
+
|
353
|
+
def t(table_name)
|
354
|
+
table = @workbook_tables[table_name]
|
355
|
+
return :ref unless table
|
356
|
+
return table if table.is_a?(Table)
|
357
|
+
@workbook_tables[table_name] = eval(table)
|
358
|
+
end
|
359
|
+
|
360
|
+
def m(*potential_excel_matrices,&block)
|
361
|
+
ExcelMatrixCollection.new(*potential_excel_matrices).matrix_map(&block)
|
362
|
+
end
|
363
|
+
|
364
|
+
def variable_name
|
365
|
+
@variable_name ||= self.class.to_s.downcase
|
366
|
+
end
|
367
|
+
|
368
|
+
def indirect(reference_text,refering_cell = nil)
|
369
|
+
parsed_reference = formula_cache[[:indirect,reference_text]] ||= Formula.parse(reference_text)
|
370
|
+
reference = parsed_reference.visit(RuntimeFormulaBuilder.new(self,refering_cell && Reference.new(refering_cell)))
|
371
|
+
formula_cache[[:indirect_result,reference]] ||= eval(reference)
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
end
|