excel_to_code 0.2.1 → 0.2.3
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.
- 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
|