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