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