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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30a048682c2cf9317de28a612f4394e205d507fb
4
- data.tar.gz: 95d2d238aab30a09195a6665f91124e2fa6a4c29
3
+ metadata.gz: ab3512a980f85d456a108268e78f6d3b77af40d6
4
+ data.tar.gz: 2e7b0e729694750dcb09b1ea0323ed3e4eef28f7
5
5
  SHA512:
6
- metadata.gz: 087a77eefc3bcbfc39b330c2bf21c7ca58e9a5dfd529db6e345ec820937a6e1d16a0ccee457f67a2f8fe001a95ba8f3f2dd29a017e462d05f6fb281f37811619
7
- data.tar.gz: 4bdda7d4a4db5e06468b16c9ed3f4e544c60757f33bcf705039677174603c8a522d462bb5011d0a470da20d4283a5fff3e018eaa04235c73f6a1b078cd86521a
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
@@ -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::Test"
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 'test/unit'"
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} < Test::Unit::TestCase"
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)
@@ -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
- # 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)
160
- self.sloppy_tests ||= true
161
- end
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 a more accesible format
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 file
188
- rewrite_worksheets
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
- # that are in the excel.
193
- simplify # Replacing shared strings and named references with their actual values, tidying arithmetic
194
-
195
- # In case this hasn't been set by the user
196
- if @cells_that_can_be_set_at_runtime.empty?
197
- log.info "Creating a good set of cells that should be settable"
198
- @cells_that_can_be_set_at_runtime = a_good_set_of_cells_that_should_be_settable_at_runtime
199
- end
200
-
201
- if named_references_that_can_be_set_at_runtime == :where_possible
202
- work_out_which_named_references_can_be_set_at_runtime
203
- end
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 = ExtractEverythingFromWorkbook.new
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
- def rewrite_worksheets
400
- rewrite_values
401
- rewrite_row_and_column_references
402
- rewrite_shared_formulae
403
- rewrite_array_formulae
404
- combine_formulae_files
405
- simplify_arithmetic
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
- def rewrite_shared_formulae
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
- # FIXME: Could now nil off the @formula_shared_targets ?
527
+ @shared_formulae_targets = :no_longer_needed # Allow the targets to be garbage collected.
442
528
  end
443
529
 
444
- def simplify_arithmetic
445
- simplify_arithmetic_replacer ||= SimplifyArithmeticAst.new
446
- @formulae.each do |ref, ast|
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
- def rewrite_values
485
- log.info "Rewriting values"
486
- r = ReplaceSharedStringAst.new(@shared_strings)
487
- @values.each do |ref, ast|
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 = a_good_set_of_cells_that_should_be_settable_at_runtime if cells_that_can_be_set == :named_references_only
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 a_good_set_of_cells_that_should_be_settable_at_runtime
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