excel_to_code 0.2.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/excel_to_c +0 -4
- data/bin/excel_to_ruby +0 -4
- data/src/commands/excel_to_c.rb +7 -27
- data/src/commands/excel_to_ruby.rb +2 -2
- data/src/commands/excel_to_x.rb +215 -175
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/excel_to_c_runtime.c +39 -806
- data/src/compile/c/excel_to_c_runtime_test.c +759 -0
- data/src/compile/ruby/map_formulae_to_ruby.rb +1 -0
- data/src/excel/excel_functions.rb +2 -0
- data/src/excel/excel_functions/lower.rb +21 -0
- data/src/extract.rb +1 -10
- data/src/extract/{extract_everything.rb → extract_data_from_worksheet.rb} +1 -1
- metadata +30 -39
- data/src/extract/check_for_unknown_functions.rb +0 -20
- data/src/extract/extract_array_formulae.rb +0 -23
- data/src/extract/extract_formulae.rb +0 -46
- data/src/extract/extract_shared_formulae.rb +0 -24
- data/src/extract/extract_shared_formulae_targets.rb +0 -15
- data/src/extract/extract_simple_formulae.rb +0 -23
- data/src/extract/extract_values.rb +0 -53
- data/src/extract/extract_worksheet_dimensions.rb +0 -21
- data/src/extract/extract_worksheet_table_relationships.rb +0 -22
- data/src/extract/simple_extract_from_xml.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab3512a980f85d456a108268e78f6d3b77af40d6
|
4
|
+
data.tar.gz: 2e7b0e729694750dcb09b1ea0323ed3e4eef28f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba0771a94b0cac9e9de877f9084df30da3c449463b3e0b916ea4834e911d918052d9d40213a5cb3286ae2ce6cff0150bbccb60b97a1171340b00ead323503171
|
7
|
+
data.tar.gz: a4118b8b83c6e0e564d0d76856ad05e170e502ff353f5f95e90486630872fee0ada8bf97ac401ae92d2e79a4f839f3e832149fff2710010e68b9f52f03103f23
|
data/bin/excel_to_c
CHANGED
@@ -39,10 +39,6 @@ END
|
|
39
39
|
command.actually_run_tests = true
|
40
40
|
end
|
41
41
|
|
42
|
-
opts.on('-m','--run-in-memory',"Instead of writing intermediate files to disk, uses memory. Requires a lot of memory") do
|
43
|
-
command.run_in_memory = true
|
44
|
-
end
|
45
|
-
|
46
42
|
opts.on('--sloppy-tests',"The generated tests treat blanks and zeros as equivalent and only require numbers to be approximately the same. This is the default.") do
|
47
43
|
command.sloppy_tests = true
|
48
44
|
end
|
data/bin/excel_to_ruby
CHANGED
@@ -35,10 +35,6 @@ END
|
|
35
35
|
command.actually_run_tests = true
|
36
36
|
end
|
37
37
|
|
38
|
-
opts.on('-m','--run-in-memory',"Instead of writing intermediate files to disk, uses memory. Requires a lot of memory.") do
|
39
|
-
command.run_in_memory = true
|
40
|
-
end
|
41
|
-
|
42
38
|
opts.on('--sloppy-tests',"The generated tests treat blanks and zeros as equivalent and only require numbers to be approximately the same. This is the default.") do
|
43
39
|
command.sloppy_tests = true
|
44
40
|
end
|
data/src/commands/excel_to_c.rb
CHANGED
@@ -19,11 +19,16 @@ class ExcelToC < ExcelToX
|
|
19
19
|
def write_out_excel_as_code
|
20
20
|
log.info "Writing C code"
|
21
21
|
|
22
|
-
number_of_refs = @formulae.size
|
22
|
+
number_of_refs = @formulae.size + @named_references_to_keep.size
|
23
23
|
|
24
24
|
# Output the workbook preamble
|
25
25
|
o = output("#{output_name.downcase}.c")
|
26
26
|
o.puts "// #{excel_file} approximately translated into C"
|
27
|
+
o.puts "// definitions"
|
28
|
+
o.puts "#define NUMBER_OF_REFS #{number_of_refs}"
|
29
|
+
o.puts "#define EXCEL_FILENAME #{excel_file.inspect}"
|
30
|
+
o.puts "// end of definitions"
|
31
|
+
o.puts
|
27
32
|
|
28
33
|
o.puts '// First we have c versions of all the excel functions that we know'
|
29
34
|
o.puts IO.readlines(File.join(File.dirname(__FILE__),'..','compile','c','excel_to_c_runtime.c')).join
|
@@ -32,34 +37,11 @@ class ExcelToC < ExcelToX
|
|
32
37
|
o.puts '// Start of the file specific functions'
|
33
38
|
o.puts
|
34
39
|
|
35
|
-
# Now we have to put all the initial definitions out
|
36
|
-
o.puts "// definitions"
|
37
|
-
o.puts "static ExcelValue ORIGINAL_EXCEL_FILENAME = {.type = ExcelString, .string = #{excel_file.inspect} };"
|
38
40
|
|
39
41
|
c = CompileToCHeader.new
|
40
42
|
c.settable = settable
|
41
43
|
c.gettable = gettable
|
42
44
|
c.rewrite(@formulae, @worksheet_c_names, o)
|
43
|
-
|
44
|
-
# Need to make sure there are enough refs for named references as well
|
45
|
-
number_of_refs += @named_references_to_keep.size
|
46
|
-
|
47
|
-
o.puts "// end of definitions"
|
48
|
-
o.puts
|
49
|
-
o.puts "// Used to decide whether to recalculate a cell"
|
50
|
-
o.puts "static int variable_set[#{number_of_refs}];"
|
51
|
-
o.puts ""
|
52
|
-
o.puts "// Used to reset all cached values and free up memory"
|
53
|
-
# FIXME: This feels like a bad place for this. Should be in runtime?
|
54
|
-
o.puts "void reset() {"
|
55
|
-
o.puts " int i;"
|
56
|
-
o.puts " cell_counter = 0;"
|
57
|
-
o.puts " free_all_allocated_memory(); "
|
58
|
-
o.puts " for(i = 0; i < #{number_of_refs}; i++) {"
|
59
|
-
o.puts " variable_set[i] = 0;"
|
60
|
-
o.puts " }"
|
61
|
-
o.puts "};"
|
62
|
-
o.puts
|
63
45
|
|
64
46
|
# Output the value constants
|
65
47
|
o.puts "// starting the value constants"
|
@@ -320,12 +302,10 @@ END
|
|
320
302
|
o = output("test_#{name}.rb")
|
321
303
|
o.puts "# coding: utf-8"
|
322
304
|
o.puts "# Test for #{name}"
|
323
|
-
o.puts "require 'rubygems'"
|
324
|
-
o.puts "gem 'minitest'"
|
325
305
|
o.puts "require 'minitest/autorun'"
|
326
306
|
o.puts "require_relative '#{output_name.downcase}'"
|
327
307
|
o.puts
|
328
|
-
o.puts "class Test#{ruby_module_name} < Minitest::
|
308
|
+
o.puts "class Test#{ruby_module_name} < Minitest::Unit::TestCase"
|
329
309
|
o.puts " def self.runnable_methods"
|
330
310
|
o.puts " puts 'Overriding minitest to run tests in a defined order'"
|
331
311
|
o.puts " methods = methods_matching(/^test_/)"
|
@@ -60,10 +60,10 @@ class ExcelToRuby < ExcelToX
|
|
60
60
|
|
61
61
|
o.puts "# coding: utf-8"
|
62
62
|
o.puts "# All tests for #{excel_file}"
|
63
|
-
o.puts "require '
|
63
|
+
o.puts "require 'minitest/autorun'"
|
64
64
|
o.puts "require_relative '#{output_name.downcase}'"
|
65
65
|
o.puts
|
66
|
-
o.puts "class Test#{ruby_module_name} <
|
66
|
+
o.puts "class Test#{ruby_module_name} < Minitest::Unit::TestCase"
|
67
67
|
o.puts " def worksheet; @worksheet ||= #{ruby_module_name}.new; end"
|
68
68
|
|
69
69
|
CompileToCUnitTest.rewrite(Hash[@references_to_test_array], sloppy_tests, @worksheet_c_names, @constants, o)
|
data/src/commands/excel_to_x.rb
CHANGED
@@ -9,7 +9,6 @@ require_relative '../excel_to_code'
|
|
9
9
|
|
10
10
|
# Used to throw normally fatal errors
|
11
11
|
class ExcelToCodeException < Exception; end
|
12
|
-
class VersionedFileNotFoundException < Exception; end
|
13
12
|
class XMLFileNotFoundException < Exception; end
|
14
13
|
|
15
14
|
class ExcelToX
|
@@ -27,10 +26,6 @@ class ExcelToX
|
|
27
26
|
# Optional attribute. The excel file will be translated to xml and stored here.
|
28
27
|
# If not specified, will be '#{excel_file_name}/xml'
|
29
28
|
attr_accessor :xml_directory
|
30
|
-
|
31
|
-
# Optional attribute. The intermediate workings will be stored here.
|
32
|
-
# If not specified, will be '#{excel_file_name}/intermediate'
|
33
|
-
attr_accessor :intermediate_directory
|
34
29
|
|
35
30
|
# Optional attribute. Specifies which cells have setters created in the c code so their values can be altered at runtime.
|
36
31
|
# It is a hash. The keys are the sheet names. The values are either the symbol :all to specify that all cells on that sheet
|
@@ -89,11 +84,6 @@ class ExcelToX
|
|
89
84
|
# * false - the generated tests are not run
|
90
85
|
attr_accessor :actually_run_tests
|
91
86
|
|
92
|
-
# Optional attribute. Boolean.
|
93
|
-
# * true - the intermediate files are not written to disk (requires a lot of memory)
|
94
|
-
# * false - the intermediate files are written to disk (default, easier to debug)
|
95
|
-
attr_accessor :run_in_memory
|
96
|
-
|
97
87
|
# This is the log file, if set it needs to respond to the same methods as the standard logger library
|
98
88
|
attr_accessor :log
|
99
89
|
|
@@ -101,75 +91,31 @@ class ExcelToX
|
|
101
91
|
# * true - empty cells and zeros are treated as being equivalent in tests. Numbers greater then 1 are only expected to match with assert_in_epsilon, numbers less than 1 are only expected to match with assert_in_delta
|
102
92
|
# * false - empty cells and zeros are treated as being different in tests. Numbers must match to full accuracy.
|
103
93
|
attr_accessor :sloppy_tests
|
104
|
-
|
105
|
-
def set_defaults
|
106
|
-
raise ExcelToCodeException.new("No excel file has been specified") unless excel_file
|
107
|
-
|
108
|
-
self.output_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),language)
|
109
|
-
self.xml_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'xml')
|
110
|
-
self.intermediate_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'intermediate')
|
111
|
-
|
112
|
-
self.output_name ||= "Excelspreadsheet"
|
113
|
-
|
114
|
-
self.cells_that_can_be_set_at_runtime ||= {}
|
115
|
-
|
116
|
-
# Make sure that all the cell names are upcase symbols and don't have any $ in them
|
117
|
-
if cells_that_can_be_set_at_runtime.is_a?(Hash)
|
118
|
-
|
119
|
-
# Make sure the sheet names are symbols
|
120
|
-
cells_that_can_be_set_at_runtime.keys.each do |sheet|
|
121
|
-
next if sheet.is_a?(Symbol)
|
122
|
-
cells_that_can_be_set_at_runtime[sheet.to_sym] = cells_that_can_be_set_at_runtime.delete(sheet)
|
123
|
-
end
|
124
|
-
|
125
|
-
cells_that_can_be_set_at_runtime.keys.each do |sheet|
|
126
|
-
next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
|
127
|
-
cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').upcase.to_sym }
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# Make sure that all the cell names are upcase symbols and don't have any $ in them
|
132
|
-
if cells_to_keep
|
133
|
-
cells_to_keep.keys.each do |sheet|
|
134
|
-
next if sheet.is_a?(Symbol)
|
135
|
-
cells_to_keep[sheet.to_sym] = cells_to_keep.delete(sheet)
|
136
|
-
end
|
137
|
-
|
138
|
-
cells_to_keep.keys.each do |sheet|
|
139
|
-
next unless cells_to_keep[sheet].is_a?(Array)
|
140
|
-
cells_to_keep[sheet] = cells_to_keep[sheet].map { |reference| reference.gsub('$','').upcase.to_sym }
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
if named_references_to_keep.is_a?(Array)
|
145
|
-
named_references_to_keep.map! { |named_reference| named_reference.downcase.to_sym }
|
146
|
-
end
|
147
|
-
|
148
|
-
if named_references_that_can_be_set_at_runtime.is_a?(Array)
|
149
|
-
named_references_that_can_be_set_at_runtime.map! { |named_reference| named_reference.downcase.to_sym }
|
150
|
-
end
|
151
|
-
|
152
|
-
# Make sure the relevant directories exist
|
153
|
-
self.excel_file = File.expand_path(excel_file)
|
154
|
-
self.output_directory = File.expand_path(output_directory)
|
155
|
-
|
156
|
-
# Set up our log file
|
157
|
-
self.log ||= Logger.new(STDOUT)
|
158
94
|
|
159
|
-
|
160
|
-
|
161
|
-
|
95
|
+
# Deprecated
|
96
|
+
def run_in_memory=(boolean)
|
97
|
+
$stderr.puts "The run_in_memory switch is deprecated (it is now always true). Please remove calls to it"
|
98
|
+
end
|
162
99
|
|
100
|
+
# Deprecated
|
101
|
+
def intermediate_directory=(dirname)
|
102
|
+
$stderr.puts "The intermediate_directory switch is deprecated (nowdays, no intermediate files are written). Please remove calls to it"
|
103
|
+
end
|
104
|
+
|
105
|
+
# This is the main method. Once all the above attributes have been set, it should be called to actually do the work.
|
163
106
|
def go!
|
164
107
|
# This sorts out the settings
|
165
108
|
set_defaults
|
109
|
+
clean_cells_that_can_be_set_at_runtime
|
110
|
+
clean_cells_to_keep
|
111
|
+
clean_named_references_to_keep
|
112
|
+
clean_named_references_that_can_be_set_at_runtime
|
166
113
|
|
167
|
-
# These turn the excel into
|
114
|
+
# These turn the excel into xml on disk
|
168
115
|
sort_out_output_directories
|
169
116
|
unzip_excel
|
170
117
|
|
171
|
-
# These get all the information out of the excel and put
|
172
|
-
# into a series of plain text files
|
118
|
+
# These get all the information out of the excel and put it in memory
|
173
119
|
extract_data_from_workbook
|
174
120
|
extract_data_from_worksheets
|
175
121
|
|
@@ -184,24 +130,33 @@ class ExcelToX
|
|
184
130
|
# * Turning range references (e.g., A1:B2) into array litterals (e.g., {A1,B1;A2,B2})
|
185
131
|
# * Turning shared formulae into a series of conventional formulae
|
186
132
|
# * Turning array formulae into a series of conventional formulae
|
187
|
-
# * Mergining all the different types of formulae and values into a single
|
188
|
-
|
133
|
+
# * Mergining all the different types of formulae and values into a single hash
|
134
|
+
rewrite_values_to_remove_shared_strings
|
135
|
+
rewrite_row_and_column_references
|
136
|
+
rewrite_shared_formulae_into_normal_formulae
|
137
|
+
rewrite_array_formulae
|
138
|
+
combine_formulae_types
|
189
139
|
|
190
140
|
# These perform a series of transformations to the information
|
191
|
-
# with the intent of removing any redundant calculations
|
192
|
-
#
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
141
|
+
# with the intent of removing any redundant calculations that are in the excel.
|
142
|
+
# Replacing shared strings and named references with their actual values, tidying arithmetic
|
143
|
+
simplify_arithmetic
|
144
|
+
simplify
|
145
|
+
|
146
|
+
# If nothing has been specified in named_references_that_can_be_set_at_runtime
|
147
|
+
# or in cells_that_can_be_set_at_runtime, then we assume that
|
148
|
+
# all value cells should be settable if they are referenced by
|
149
|
+
# any other forumla.
|
150
|
+
ensure_there_is_a_good_set_of_cells_that_can_be_set_at_runtime
|
151
|
+
|
152
|
+
# If named_reference_that_have_been_set_at_runtime is given the :where_possible switch
|
153
|
+
# then will take a look at which named_references only refer to cells that have been
|
154
|
+
# specifed or judged as settable
|
155
|
+
work_out_which_named_references_can_be_set_at_runtime
|
156
|
+
|
157
|
+
# Slims down the named references we keep track of to just the ones that should
|
158
|
+
# appear in the generated code: basically those that are specifed as being gettable
|
159
|
+
# or specified or judged to be settable.
|
205
160
|
filter_named_references
|
206
161
|
|
207
162
|
replace_formulae_with_their_results
|
@@ -221,11 +176,82 @@ class ExcelToX
|
|
221
176
|
log.info "The generated code is available in #{File.join(output_directory)}"
|
222
177
|
end
|
223
178
|
|
179
|
+
# If an attribute hasn't been specified, specifies a good default value here.
|
180
|
+
def set_defaults
|
181
|
+
raise ExcelToCodeException.new("No excel file has been specified") unless excel_file
|
182
|
+
|
183
|
+
self.output_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),language)
|
184
|
+
self.xml_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'xml')
|
185
|
+
|
186
|
+
self.output_name ||= "Excelspreadsheet"
|
187
|
+
|
188
|
+
self.cells_that_can_be_set_at_runtime ||= {}
|
189
|
+
|
190
|
+
# Make sure the relevant directories exist
|
191
|
+
self.excel_file = File.expand_path(excel_file)
|
192
|
+
self.output_directory = File.expand_path(output_directory)
|
193
|
+
|
194
|
+
# Set up our log file
|
195
|
+
self.log ||= Logger.new(STDOUT)
|
196
|
+
|
197
|
+
# By default, tests allow empty cells and zeros to be treated as equivalent, and numbers only have to match to a 0.001 epsilon (if expected>1) or 0.001 delta (if expected<1)
|
198
|
+
self.sloppy_tests ||= true
|
199
|
+
end
|
200
|
+
|
201
|
+
# Make sure that sheet names are symbols FIXME: Case ?
|
202
|
+
# Make sure that all the cell names are upcase symbols and don't have any $ in them
|
203
|
+
def clean_cells_that_can_be_set_at_runtime
|
204
|
+
return unless cells_that_can_be_set_at_runtime.is_a?(Hash)
|
205
|
+
|
206
|
+
# Make sure sheet names are symbols
|
207
|
+
cells_that_can_be_set_at_runtime.keys.each do |sheet|
|
208
|
+
next if sheet.is_a?(Symbol)
|
209
|
+
cells_that_can_be_set_at_runtime[sheet.to_sym] = cells_that_can_be_set_at_runtime.delete(sheet)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Make sure references are of the form A1, not a1 or A$1
|
213
|
+
cells_that_can_be_set_at_runtime.keys.each do |sheet|
|
214
|
+
next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
|
215
|
+
cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map do |reference|
|
216
|
+
reference.gsub('$','').upcase.to_sym
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Make sure that sheet names are symbols FIXME: Case ?
|
222
|
+
# Make sure that all the cell names are upcase symbols and don't have any $ in them
|
223
|
+
def clean_cells_to_keep
|
224
|
+
return unless cells_to_keep
|
225
|
+
|
226
|
+
# Make sure sheet names are symbols
|
227
|
+
cells_to_keep.keys.each do |sheet|
|
228
|
+
next if sheet.is_a?(Symbol)
|
229
|
+
cells_to_keep[sheet.to_sym] = cells_to_keep.delete(sheet)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Make sure references are of the form A1, not a1 or A$1
|
233
|
+
cells_to_keep.keys.each do |sheet|
|
234
|
+
next unless cells_to_keep[sheet].is_a?(Array)
|
235
|
+
cells_to_keep[sheet] = cells_to_keep[sheet].map { |reference| reference.gsub('$','').upcase.to_sym }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Make sure named_references_to_keep are lowercase symbols
|
240
|
+
def clean_named_references_to_keep
|
241
|
+
return unless named_references_to_keep.is_a?(Array)
|
242
|
+
named_references_to_keep.map! { |named_reference| named_reference.downcase.to_sym }
|
243
|
+
end
|
244
|
+
|
245
|
+
# Make sure named_references_that_can_be_set_at_runtime are lowercase symbols
|
246
|
+
def clean_named_references_that_can_be_set_at_runtime
|
247
|
+
return unless named_references_that_can_be_set_at_runtime.is_a?(Array)
|
248
|
+
named_references_that_can_be_set_at_runtime.map! { |named_reference| named_reference.downcase.to_sym }
|
249
|
+
end
|
250
|
+
|
224
251
|
# Creates any directories that are needed
|
225
252
|
def sort_out_output_directories
|
226
253
|
FileUtils.mkdir_p(output_directory)
|
227
254
|
FileUtils.mkdir_p(xml_directory)
|
228
|
-
FileUtils.mkdir_p(intermediate_directory) unless run_in_memory
|
229
255
|
end
|
230
256
|
|
231
257
|
# FIXME: Replace these with pure ruby versions?
|
@@ -344,13 +370,12 @@ class ExcelToX
|
|
344
370
|
@c_names_assigned[c_name] = name
|
345
371
|
c_name
|
346
372
|
end
|
347
|
-
|
348
373
|
|
349
374
|
# For each worksheet, extract the useful bits from the excel xml
|
350
375
|
def extract_data_from_worksheets
|
351
376
|
# All are hashes of the format ["SheetName", "A1"] => [:number, "1"]
|
352
377
|
# This one has a series of table references
|
353
|
-
extractor =
|
378
|
+
extractor = ExtractDataFromWorksheet.new
|
354
379
|
|
355
380
|
# Loop through the worksheets
|
356
381
|
# FIXME: make xml_filename be the IO object?
|
@@ -396,13 +421,72 @@ class ExcelToX
|
|
396
421
|
end
|
397
422
|
end
|
398
423
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
424
|
+
# This makes sure that cells_to_keep includes named_references_to_keep
|
425
|
+
def transfer_named_references_to_keep_into_cells_to_keep
|
426
|
+
log.info "Transfering named references to keep into cells to keep"
|
427
|
+
return unless @named_references_to_keep
|
428
|
+
@named_references_to_keep = @named_references.keys if @named_references_to_keep == :all
|
429
|
+
@cells_to_keep ||= {}
|
430
|
+
@named_references_to_keep.each do |name|
|
431
|
+
ref = @named_references[name]
|
432
|
+
if ref
|
433
|
+
add_ref_to_hash(ref, @cells_to_keep)
|
434
|
+
else
|
435
|
+
log.warn "Named reference \"#{name}\" not found"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# This makes sure that there are cell setter methods for any named references that can be set
|
441
|
+
def transfer_named_references_that_can_be_set_at_runtime_into_cells_that_can_be_set_at_runtime
|
442
|
+
log.info "Making sure there are setter methods for named references that can be set"
|
443
|
+
return unless @named_references_that_can_be_set_at_runtime
|
444
|
+
return if @named_references_that_can_be_set_at_runtime == :where_possible # in this case will be done in #work_out_which_named_references_can_be_set_at_runtime
|
445
|
+
@cells_that_can_be_set_at_runtime ||= {}
|
446
|
+
@named_references_that_can_be_set_at_runtime.each do |name|
|
447
|
+
ref = @named_references[name]
|
448
|
+
if ref
|
449
|
+
add_ref_to_hash(ref, @cells_that_can_be_set_at_runtime)
|
450
|
+
else
|
451
|
+
log.warn "Named reference #{name} not found"
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# The reference passed may be a sheet reference or an area reference
|
457
|
+
# in which case we need to expand out the ref so that the hash contains
|
458
|
+
# one reference per cell
|
459
|
+
def add_ref_to_hash(ref, hash)
|
460
|
+
ref = ref.dup
|
461
|
+
if ref.first == :sheet_reference
|
462
|
+
sheet = ref[1]
|
463
|
+
cell = Reference.for(ref[2][1]).unfix.to_sym
|
464
|
+
hash[sheet] ||= []
|
465
|
+
return if hash[sheet] == :all
|
466
|
+
hash[sheet] << cell.to_sym unless hash[sheet].include?(cell.to_sym)
|
467
|
+
elsif ref.first == :array
|
468
|
+
ref.shift
|
469
|
+
ref.each do |row|
|
470
|
+
row = row.dup
|
471
|
+
row.shift
|
472
|
+
row.each do |cell|
|
473
|
+
add_ref_to_hash(cell, hash)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
else
|
477
|
+
log.error "Weird reference in named reference #{ref}"
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
# Excel can include references to strings rather than the strings
|
482
|
+
# themselves. This harmonises so the strings themselves are always
|
483
|
+
# used.
|
484
|
+
def rewrite_values_to_remove_shared_strings
|
485
|
+
log.info "Rewriting values"
|
486
|
+
r = ReplaceSharedStringAst.new(@shared_strings)
|
487
|
+
@values.each do |ref, ast|
|
488
|
+
r.map(ast)
|
489
|
+
end
|
406
490
|
end
|
407
491
|
|
408
492
|
# In Excel we can have references like A:Z and 5:20 which mean all cells in columns
|
@@ -435,19 +519,17 @@ class ExcelToX
|
|
435
519
|
# FIXME: Could we now nil off the dimensions? Or do we need for indirects?
|
436
520
|
end
|
437
521
|
|
438
|
-
|
522
|
+
# Excel can share formula definitions across cells. This function unshares
|
523
|
+
# them so every cell has its own definition
|
524
|
+
def rewrite_shared_formulae_into_normal_formulae
|
439
525
|
log.info "Rewriting shared formulae"
|
440
526
|
@formulae_shared = RewriteSharedFormulae.rewrite( @formulae_shared, @formulae_shared_targets)
|
441
|
-
|
527
|
+
@shared_formulae_targets = :no_longer_needed # Allow the targets to be garbage collected.
|
442
528
|
end
|
443
529
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
simplify_arithmetic_replacer.map(ast)
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
530
|
+
# Excel has the concept of array formulae: formulae whose answer spans
|
531
|
+
# many cells. They are awkward. We try and replace them with conventional
|
532
|
+
# formulae here.
|
451
533
|
def rewrite_array_formulae
|
452
534
|
log.info "Rewriting array formulae"
|
453
535
|
# FIMXE: Refactor this
|
@@ -481,16 +563,10 @@ class ExcelToX
|
|
481
563
|
@formulae_array = RewriteArrayFormulae.rewrite(@formulae_array)
|
482
564
|
end
|
483
565
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
r.map(ast)
|
489
|
-
end
|
490
|
-
end
|
491
|
-
|
492
|
-
def combine_formulae_files
|
493
|
-
log.info "Combining formula files"
|
566
|
+
# At the end of this function we are left with a single @formulae hash
|
567
|
+
# that contains every cell in the workbook, whatever its original format.
|
568
|
+
def combine_formulae_types
|
569
|
+
log.info "Combining formulae types"
|
494
570
|
|
495
571
|
@formulae = required_references
|
496
572
|
# We dup this to avoid the values being replaced when manipulating formulae
|
@@ -504,6 +580,16 @@ class ExcelToX
|
|
504
580
|
log.info "Sheet contains #{@formulae.size} cells"
|
505
581
|
end
|
506
582
|
|
583
|
+
# Turns aritmetic with many arguments (1+2+3+4) into arithmetic with only
|
584
|
+
# two arguments (((1+2)+3)+4), taking into account operator precedence.
|
585
|
+
def simplify_arithmetic
|
586
|
+
log.info "Simplifying arithmetic"
|
587
|
+
simplify_arithmetic_replacer ||= SimplifyArithmeticAst.new
|
588
|
+
@formulae.each do |ref, ast|
|
589
|
+
simplify_arithmetic_replacer.map(ast)
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
507
593
|
# This ensures that all gettable and settable values appear in the output
|
508
594
|
# even if they are blank in the underlying excel
|
509
595
|
def required_references
|
@@ -560,71 +646,13 @@ class ExcelToX
|
|
560
646
|
required_refs
|
561
647
|
end
|
562
648
|
|
563
|
-
# This makes sure that cells_to_keep includes named_references_to_keep
|
564
|
-
def transfer_named_references_to_keep_into_cells_to_keep
|
565
|
-
log.info "Transfering named references to keep into cells to keep"
|
566
|
-
return unless @named_references_to_keep
|
567
|
-
@named_references_to_keep = @named_references.keys if @named_references_to_keep == :all
|
568
|
-
@cells_to_keep ||= {}
|
569
|
-
@named_references_to_keep.each do |name|
|
570
|
-
ref = @named_references[name]
|
571
|
-
if ref
|
572
|
-
add_ref_to_hash(ref, @cells_to_keep)
|
573
|
-
p @cells_to_keep
|
574
|
-
else
|
575
|
-
log.warn "Named reference \"#{name}\" not found"
|
576
|
-
end
|
577
|
-
end
|
578
|
-
end
|
579
|
-
|
580
|
-
# This makes sure that there are cell setter methods for any named references that can be set
|
581
|
-
def transfer_named_references_that_can_be_set_at_runtime_into_cells_that_can_be_set_at_runtime
|
582
|
-
log.info "Making sure there are setter methods for named references that can be set"
|
583
|
-
return unless @named_references_that_can_be_set_at_runtime
|
584
|
-
return if @named_references_that_can_be_set_at_runtime == :where_possible # in this case will be done in #work_out_which_named_references_can_be_set_at_runtime
|
585
|
-
@cells_that_can_be_set_at_runtime ||= {}
|
586
|
-
@named_references_that_can_be_set_at_runtime.each do |name|
|
587
|
-
ref = @named_references[name]
|
588
|
-
if ref
|
589
|
-
add_ref_to_hash(ref, @cells_that_can_be_set_at_runtime)
|
590
|
-
else
|
591
|
-
log.warn "Named reference #{name} not found"
|
592
|
-
end
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
|
-
# The reference passed may be a sheet reference or an area reference
|
597
|
-
# in which case we need to expand out the ref so that the hash contains
|
598
|
-
# one reference per cell
|
599
|
-
def add_ref_to_hash(ref, hash)
|
600
|
-
ref = ref.dup
|
601
|
-
if ref.first == :sheet_reference
|
602
|
-
sheet = ref[1]
|
603
|
-
cell = Reference.for(ref[2][1]).unfix.to_sym
|
604
|
-
hash[sheet] ||= []
|
605
|
-
return if hash[sheet] == :all
|
606
|
-
hash[sheet] << cell.to_sym unless hash[sheet].include?(cell.to_sym)
|
607
|
-
elsif ref.first == :array
|
608
|
-
ref.shift
|
609
|
-
ref.each do |row|
|
610
|
-
row = row.dup
|
611
|
-
row.shift
|
612
|
-
row.each do |cell|
|
613
|
-
add_ref_to_hash(cell, hash)
|
614
|
-
end
|
615
|
-
end
|
616
|
-
else
|
617
|
-
log.error "Weird reference in named reference #{ref}"
|
618
|
-
end
|
619
|
-
end
|
620
|
-
|
621
649
|
# This just checks which named references refer to cells that we have already declared as settable
|
622
650
|
def work_out_which_named_references_can_be_set_at_runtime
|
623
651
|
log.info "Working out which named references can be set at runtime"
|
624
652
|
return unless @named_references_that_can_be_set_at_runtime
|
625
653
|
return unless @named_references_that_can_be_set_at_runtime == :where_possible
|
626
654
|
cells_that_can_be_set = @cells_that_can_be_set_at_runtime
|
627
|
-
cells_that_can_be_set =
|
655
|
+
cells_that_can_be_set = cells_with_settable_values if cells_that_can_be_set == :named_references_only
|
628
656
|
cells_that_can_be_set_due_to_named_reference = Hash.new { |h,k| h[k] = Array.new }
|
629
657
|
@named_references_that_can_be_set_at_runtime = []
|
630
658
|
all_named_references = @named_references
|
@@ -979,7 +1007,19 @@ class ExcelToX
|
|
979
1007
|
# or in cells_that_can_be_set_at_runtime, then we assume that
|
980
1008
|
# all value cells should be settable if they are referenced by
|
981
1009
|
# any other forumla.
|
982
|
-
def
|
1010
|
+
def ensure_there_is_a_good_set_of_cells_that_can_be_set_at_runtime
|
1011
|
+
# By this stage, if named_references were set, then cells_that_can_be_set_at_runtime will
|
1012
|
+
# have been set to match
|
1013
|
+
return unless @cells_that_can_be_set_at_runtime.empty?
|
1014
|
+
@cells_that_can_be_set_at_runtime = cells_with_settable_values
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
# Returns a list of cells that are:
|
1018
|
+
# 1. Simple values (e.g., a string or a number)
|
1019
|
+
# 2. That are referenced in other formulae
|
1020
|
+
# ... these are likely to be cells that the user will want to use as inputs
|
1021
|
+
# to their calculation.
|
1022
|
+
def cells_with_settable_values
|
983
1023
|
log.info "Generating a good set of cells that should be settable"
|
984
1024
|
|
985
1025
|
counter = CountFormulaReferences.new
|