excel_to_code 0.2.18 → 0.2.19

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: 337fc100f3818bf193414bcd8fb3aaec557e1a6b
4
- data.tar.gz: 34218ff7c787a63a1e0081b688ff328d1235316e
3
+ metadata.gz: 74698377e3e89c2dae52564440694e4e2e97f2f1
4
+ data.tar.gz: 73e3a3af8271ac02a3f68727f43e5874153d2d2f
5
5
  SHA512:
6
- metadata.gz: 10b5b04a5fe6ae0b5df092a010b5cbf0cad48025db71b99350da7a040e2992c5301c08f23652040b4eb340fcb2f064aa7bae7c02b765c53c8849e11480a66a71
7
- data.tar.gz: 3ec3fbfbad7b341990559ae40d121dfdbd46839aa3f92f5a1b41b58ce597e43606d91de146bc940d5976b682320b593925bea0619463f9e183b98800f0b15a5c
6
+ metadata.gz: f97a19688abe21b1cdbc7755a77158379746f012bda38154b35c7114add213ef4d1cee34f6121ce61d09d3bced7ae5de98f413e297ce5c0beecc9bd586a7177d
7
+ data.tar.gz: b911c226eed1bd735828b5a80927b8198ea1b226b312032c937c9bb96a98f10ccb3bff9fc13e391c2aef0c3163307e3b1e541e371c74459ae628ac714aa34027
@@ -228,10 +228,16 @@ class ExcelToX
228
228
 
229
229
  # Setting this to false may make it easier to figure out errors
230
230
  self.extract_repeated_parts_of_formulae = true if @extract_repeated_parts_of_formulae == nil
231
+
232
+ # This setting is used for debugging, and makes the system only do the conversion on a subset of the the
231
233
  if self.isolate
232
- self.cells_to_keep = { isolate.to_s => :all }
233
- self.isolate = self.isolate.to_sym
234
- log.warn "Isolating #{@isolate} worksheet. No other sheets will be converted"
234
+ self.isolate = [self.isolate] unless self.isolate.is_a?(Array)
235
+ self.cells_to_keep ||= {}
236
+ self.isolate.each do |sheet|
237
+ self.cells_to_keep[sheet.to_s] = :all
238
+ end
239
+ self.isolate = self.isolate.map { |s| s.to_sym }
240
+ log.warn "Isolating #{@isolate} worksheet(s). No other sheets will be converted"
235
241
  end
236
242
  end
237
243
 
@@ -283,6 +289,7 @@ class ExcelToX
283
289
  end
284
290
  # Then we parse them
285
291
  @named_references.each do |name, reference|
292
+ begin
286
293
  parsed = CachingFormulaParser.parse(reference)
287
294
  if parsed
288
295
  @named_references[name] = parsed
@@ -290,6 +297,10 @@ class ExcelToX
290
297
  $stderr.puts "Named reference #{name} #{reference} not parsed"
291
298
  exit
292
299
  end
300
+ rescue Exception
301
+ $stderr.puts "Named reference #{name} #{reference} not parsed"
302
+ raise
303
+ end
293
304
  end
294
305
  # Replace A$1:B2 with [A1, A2, B1, B2]
295
306
  @replace_ranges_with_array_literals_replacer ||= ReplaceRangesWithArrayLiteralsAst.new
@@ -456,7 +467,8 @@ class ExcelToX
456
467
  # This is used in debugging large worksheets to limit
457
468
  # the optimisation to a particular worksheet
458
469
  if isolate
459
- extractor.only_extract_values = (isolate != name)
470
+ log.info "Only extracting values from #{name}: #{!isolate.include?(name)}"
471
+ extractor.only_extract_values = !isolate.include?(name)
460
472
  end
461
473
 
462
474
  log.info "Extracting data from #{name}"
@@ -648,10 +660,13 @@ class ExcelToX
648
660
  # FIXME: THIS IS THE MOST HORRIFIC BODGE. I HATE IT.
649
661
  emergency_indirect_replacement_bodge = EmergencyArrayFormulaReplaceIndirectBodge.new
650
662
  emergency_indirect_replacement_bodge.references = @values
663
+ emergency_indirect_replacement_bodge.tables = @tables
664
+ emergency_indirect_replacement_bodge.named_references = @named_references
651
665
 
652
666
  @formulae_array.each do |ref, details|
653
667
  @shared_string_replacer.map(details.last)
654
668
  emergency_indirect_replacement_bodge.current_sheet_name = ref.first
669
+ emergency_indirect_replacement_bodge.referring_cell = ref.last
655
670
  emergency_indirect_replacement_bodge.replace(details.last)
656
671
 
657
672
  named_reference_replacer.default_sheet_name = ref.first
@@ -930,22 +945,27 @@ class ExcelToX
930
945
  references_that_need_updating = {}
931
946
 
932
947
  @cells_with_formulae.each do |ref, ast|
933
- # FIXME: Shouldn't need to wrap ref.fist in an array
934
- inline_replacer.current_sheet_name = [ref.first]
935
- inline_replacer.map(ast)
936
- # If a formula references a cell containing a value, the reference is replaced with the value (e.g., if A1 := 2 and A2 := A1 + 1 then becomes: A2 := 2 + 1)
937
- value_replacer.map(ast)
938
- column_and_row_function_replacement.current_reference = ref.last
939
- if column_and_row_function_replacement.replace(ast)
940
- references_that_need_updating[ref] = ast
941
- end
942
- if offset_replacement.replace(ast)
943
- references_that_need_updating[ref] = ast
944
- end
945
- if indirect_replacement.replace(ast)
946
- references_that_need_updating[ref] = ast
948
+ begin
949
+ # FIXME: Shouldn't need to wrap ref.fist in an array
950
+ inline_replacer.current_sheet_name = [ref.first]
951
+ inline_replacer.map(ast)
952
+ # If a formula references a cell containing a value, the reference is replaced with the value (e.g., if A1 := 2 and A2 := A1 + 1 then becomes: A2 := 2 + 1)
953
+ value_replacer.map(ast)
954
+ column_and_row_function_replacement.current_reference = ref.last
955
+ if column_and_row_function_replacement.replace(ast)
956
+ references_that_need_updating[ref] = ast
957
+ end
958
+ if offset_replacement.replace(ast)
959
+ references_that_need_updating[ref] = ast
960
+ end
961
+ if indirect_replacement.replace(ast)
962
+ references_that_need_updating[ref] = ast
963
+ end
964
+ @cells_with_formulae.delete(ref) if VALUE_TYPE[ast[0]]
965
+ rescue Exception => e
966
+ log.fatal "Exception when replacing formulae with results in #{ref}: #{ast}"
967
+ raise
947
968
  end
948
- @cells_with_formulae.delete(ref) if VALUE_TYPE[ast[0]]
949
969
  end
950
970
 
951
971
  simplify(references_that_need_updating)
@@ -1032,8 +1052,13 @@ class ExcelToX
1032
1052
  r.references = @formulae
1033
1053
  r.inline_ast = inline_ast_decision
1034
1054
  @cells_with_formulae.each do |ref, ast|
1035
- r.current_sheet_name = [ref.first]
1036
- r.map(ast)
1055
+ begin
1056
+ r.current_sheet_name = [ref.first]
1057
+ r.map(ast)
1058
+ rescue Exception => e
1059
+ log.fatal "Exception when inlining formulae only used once in #{ref}: #{ast}"
1060
+ raise
1061
+ end
1037
1062
  end
1038
1063
  end
1039
1064
 
data/src/compile/c/a.out CHANGED
Binary file
@@ -308,6 +308,9 @@ static ExcelValue ensure_is_number(ExcelValue maybe_number_v) {
308
308
  if(maybe_number_v.type == ExcelNumber) {
309
309
  return maybe_number_v;
310
310
  }
311
+ if(maybe_number_v.type == ExcelError) {
312
+ return maybe_number_v;
313
+ }
311
314
  NUMBER(maybe_number_v, maybe_number)
312
315
  CHECK_FOR_CONVERSION_ERROR
313
316
  return new_excel_number(maybe_number);
@@ -847,7 +850,7 @@ static ExcelValue excel_match(ExcelValue lookup_value, ExcelValue lookup_array,
847
850
  }
848
851
 
849
852
  static ExcelValue excel_match_2(ExcelValue lookup_value, ExcelValue lookup_array ) {
850
- return excel_match(lookup_value, lookup_array, ZERO);
853
+ return excel_match(lookup_value, lookup_array, ONE);
851
854
  }
852
855
 
853
856
  static ExcelValue find(ExcelValue find_text_v, ExcelValue within_text_v, ExcelValue start_number_v) {
@@ -1615,9 +1618,10 @@ static ExcelValue subtotal(ExcelValue subtotal_type_v, int number_of_arguments,
1615
1618
  }
1616
1619
 
1617
1620
 
1621
+ // FIXME: Check if this deals with errors correctly
1618
1622
  static ExcelValue filter_range(ExcelValue original_range_v, int number_of_arguments, ExcelValue *arguments) {
1619
1623
  // First, set up the original_range
1620
- CHECK_FOR_PASSED_ERROR(original_range_v);
1624
+ //CHECK_FOR_PASSED_ERROR(original_range_v);
1621
1625
 
1622
1626
  // Set up the sum range
1623
1627
  ExcelValue *original_range;
@@ -115,11 +115,9 @@ int test_functions() {
115
115
  ExcelValue excel_match_array_5_v = new_excel_range(excel_match_array_5,1,3);
116
116
 
117
117
  // Two argument version
118
- assert(excel_match_2(new_excel_number(10),excel_match_array_1_v).number == 1);
119
- assert(excel_match_2(new_excel_number(100),excel_match_array_1_v).number == 2);
120
- assert(excel_match_2(new_excel_number(1000),excel_match_array_1_v).type == ExcelError);
121
- assert(excel_match_2(new_excel_number(0), excel_match_array_4_v).number == 2);
122
- assert(excel_match_2(BLANK, excel_match_array_5_v).number == 2);
118
+ assert(excel_match_2(new_excel_number(14),excel_match_array_1_v).number == 1);
119
+ assert(excel_match_2(new_excel_number(110),excel_match_array_1_v).number == 2);
120
+ assert(excel_match_2(new_excel_number(-10),excel_match_array_1_v).type == ExcelError);
123
121
 
124
122
  // Three argument version
125
123
  assert(excel_match(new_excel_number(10.0), excel_match_array_1_v, new_excel_number(0) ).number == 1);
@@ -512,7 +510,8 @@ int test_functions() {
512
510
  assert(sumifs(new_excel_number(100),2,sumifs_array_14).number == 0);
513
511
 
514
512
  // ... should return an error if range argument is an error
515
- assert(sumifs(REF,2,sumifs_array_13).type == ExcelError);
513
+ ExcelValue sumifs_array_15[] = {ONE, ONE};
514
+ assert(sumifs(REF,2,sumifs_array_15).type == ExcelError);
516
515
 
517
516
 
518
517
  // Test SUMIF
@@ -20,7 +20,7 @@ class MapValuesToC
20
20
  end
21
21
 
22
22
  def inlined_blank
23
- "ZERO"
23
+ "BLANK"
24
24
  end
25
25
 
26
26
  def constant(name)
@@ -30,16 +30,7 @@ class MapValuesToC
30
30
  alias :null :blank
31
31
 
32
32
  def number(text)
33
- n = case text
34
- when /\./
35
- text.to_f.to_s
36
- when /e/i
37
- text.to_f.to_s
38
- else
39
- text.to_i.to_s
40
- end
41
-
42
- case n
33
+ case text.to_f
43
34
  when 0; "ZERO"
44
35
  when 1; "ONE"
45
36
  when 2; "TWO"
@@ -52,6 +43,14 @@ class MapValuesToC
52
43
  when 9; "NINE"
53
44
  when 10; "TEN"
54
45
  else
46
+ n = case text
47
+ when /\./
48
+ text.to_f.to_s
49
+ when /e/i
50
+ text.to_f.to_s
51
+ else
52
+ text.to_i.to_s
53
+ end
55
54
  "new_excel_number(#{n})"
56
55
  end
57
56
  end
@@ -1,76 +1,10 @@
1
1
  module ExcelFunctions
2
2
 
3
- def averageifs(average_range, *criteria)
4
- # First, get rid of the errors
5
- return average_range if average_range.is_a?(Symbol)
6
- error = criteria.find { |a| a.is_a?(Symbol) }
7
- return error if error
8
-
9
- # Sort out the average range
10
- average_range = [average_range] unless average_range.is_a?(Array)
11
- average_range = average_range.flatten
12
-
13
- # Sort out the criteria
14
- 0.step(criteria.length-1,2).each do |i|
15
- if criteria[i].is_a?(Array)
16
- criteria[i] = criteria[i].flatten
17
- else
18
- criteria[i] = [criteria[i]]
19
- end
20
- end
21
-
22
- # This will hold the items that pass the test
23
- accumulator = []
24
-
25
- # Work through each part of the average range
26
- average_range.each_with_index do |potential_average,index|
27
- next unless potential_average.is_a?(Numeric)
28
-
29
- # If a criteria fails, this is set to false and no further criteria are evaluated
30
- pass = true
31
-
32
- 0.step(criteria.length-1,2).each do |i|
33
- check_range = criteria[i]
34
- required_value = criteria[i+1]
35
- return :value if index >= check_range.length
36
- check_value = check_range[index]
37
-
38
- pass = case check_value
39
- when String
40
- check_value.downcase == required_value.to_s.downcase
41
- when true, false
42
- check_value == required_value
43
- when nil
44
- required_value == ""
45
- when Numeric
46
- case required_value
47
- when Numeric
48
- check_value == required_value
49
- when String
50
- required_value =~ /^(<=|>=|<|>)?([-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?)$/
51
- if $1 && $2
52
- check_value.send($1,$2.to_f)
53
- elsif $2
54
- check_value == $2.to_f
55
- else
56
- false
57
- end
58
- else
59
- check_value == required_value
60
- end
61
- end # case check_value
62
-
63
- break unless pass
64
- end # criteria loop
65
-
66
- accumulator << potential_average if pass
67
-
68
- end
3
+ # See sumifs.rb for _filtered_range
69
4
 
70
- accumulator.delete_if { |a| !a.is_a?(Numeric) }
71
- return :div0 if accumulator.empty?
72
- accumulator.inject(0.0) { |m,i| m + i.to_f } / accumulator.size.to_f
73
-
5
+ def averageifs(average_range, *criteria)
6
+ filtered = _filtered_range(average_range, *criteria)
7
+ average(*filtered)
74
8
  end
75
9
 
76
10
  end
@@ -1,6 +1,9 @@
1
1
  module ExcelFunctions
2
2
 
3
- def excel_match(lookup_value,lookup_array,match_type = 0)
3
+ # FIXME: Excel doesn't use this algorithm for matching. Seems
4
+ # to do some bisecting. Only matters if doing an ordered search
5
+ # and the range is not in order
6
+ def excel_match(lookup_value,lookup_array,match_type = 1)
4
7
  return lookup_value if lookup_value.is_a?(Symbol)
5
8
  return lookup_array if lookup_array.is_a?(Symbol)
6
9
  return match_type if match_type.is_a?(Symbol)
@@ -1,14 +1,9 @@
1
1
  module ExcelFunctions
2
-
3
- def sumifs(sum_range,*criteria)
4
- # First, get rid of the errors
5
- return sum_range if sum_range.is_a?(Symbol)
6
- error = criteria.find { |a| a.is_a?(Symbol) }
7
- return error if error
8
-
2
+
3
+ def _filtered_range(range, *criteria)
9
4
  # Sort out the sum range
10
- sum_range = [sum_range] unless sum_range.is_a?(Array)
11
- sum_range = sum_range.flatten
5
+ range = [range] unless range.is_a?(Array)
6
+ range = range.flatten
12
7
 
13
8
  # Sort out the criteria
14
9
  0.step(criteria.length-1,2).each do |i|
@@ -18,13 +13,12 @@ module ExcelFunctions
18
13
  criteria[i] = [criteria[i]]
19
14
  end
20
15
  end
21
-
22
- # This will be the final answer
23
- accumulator = 0
16
+
17
+ filtered = []
24
18
 
25
19
  # Work through each part of the sum range
26
- sum_range.each_with_index do |potential_sum,index|
27
- next unless potential_sum.is_a?(Numeric)
20
+ range.each_with_index do |potential,index|
21
+ #next unless potential.is_a?(Numeric)
28
22
 
29
23
  # If a criteria fails, this is set to false and no further criteria are evaluated
30
24
  pass = true
@@ -58,17 +52,22 @@ module ExcelFunctions
58
52
  else
59
53
  check_value == required_value
60
54
  end
55
+ when Symbol
56
+ check_value == required_value
61
57
  end # case check_value
62
58
 
63
59
  break unless pass
64
60
  end # criteria loop
65
61
 
66
- accumulator += potential_sum if pass
67
-
68
- end
69
-
70
- return accumulator
71
-
62
+ filtered << potential if pass
72
63
  end
64
+
65
+ return filtered
66
+ end
67
+
68
+ def sumifs(range,*criteria)
69
+ filtered = _filtered_range(range,*criteria)
70
+ sum(*filtered)
71
+ end
73
72
 
74
73
  end
@@ -18,7 +18,7 @@ class Formula < RubyPeg
18
18
  end
19
19
 
20
20
  def thing
21
- function || array || brackets || any_reference || string || percentage || number || boolean || prefix || error || named_reference
21
+ percentage || function || array || brackets || any_reference || string || number || boolean || prefix || error || named_reference
22
22
  end
23
23
 
24
24
  def argument
@@ -93,7 +93,7 @@ class Formula < RubyPeg
93
93
 
94
94
  def percentage
95
95
  node :percentage do
96
- terminal(/[-+]?[0-9]+\.?[0-9]*/) && ignore { terminal("%") }
96
+ (terminal(/[-+]?[0-9]+\.?[0-9]*/) || function) && ignore { terminal("%") }
97
97
  end
98
98
  end
99
99
 
@@ -1,6 +1,6 @@
1
1
  formula := space? expression+
2
2
  expression = string_join | comparison | arithmetic | thing
3
- thing = function | array | brackets | any_reference | string | percentage | number | boolean | prefix | error | named_reference
3
+ thing = percentage | function | array | brackets | any_reference | string | number | boolean | prefix | error | named_reference
4
4
  argument = expression | null
5
5
  function := /[A-Z]+/ `'(' space argument? (space `',' space argument)* space `')'
6
6
  brackets := `'(' space expression+ space `')'
@@ -14,7 +14,7 @@ comparator := '>=' | '<=' | '<>' | '>' | '<' | '='
14
14
  string := `'"' /(""|[^"])*/ `'"'
15
15
  any_reference = external_reference | any_internal_reference
16
16
  any_internal_reference = table_reference | local_table_reference | sheet_reference | sheetless_reference
17
- percentage := /[-+]?[0-9]+\.?[0-9]*/ `'%'
17
+ percentage := (/[-+]?[0-9]+\.?[0-9]*/ | function) `'%'
18
18
  number := /[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/
19
19
  operator := '+' | '-' | '/' | '*' | '^'
20
20
  external_reference := /\[\d+\]!?/ (any_internal_reference | named_reference)
data/src/excel/table.rb CHANGED
@@ -17,14 +17,14 @@ class Table
17
17
 
18
18
  case structured_reference
19
19
  when /\[#Headers\],\[(.*?)\]:\[(.*?)\]/io
20
- column_number_start = @column_name_array.find_index($1.strip.downcase)
21
- column_number_finish = @column_name_array.find_index($2.strip.downcase)
20
+ column_number_start = column_number_for($1)
21
+ column_number_finish = column_number_for($2)
22
22
  return ref_error unless column_number_start && column_number_finish
23
23
  ast_for_area @area.excel_start.offset(0,column_number_start), @area.excel_start.offset(0,column_number_finish)
24
24
 
25
25
  when /\[#Totals\],\[(.*?)\]:\[(.*?)\]/io
26
- column_number_start = @column_name_array.find_index($1.strip.downcase)
27
- column_number_finish = @column_name_array.find_index($2.strip.downcase)
26
+ column_number_start = column_number_for($1)
27
+ column_number_finish = column_number_for($2)
28
28
  return ref_error unless column_number_start && column_number_finish
29
29
  ast_for_area @area.excel_start.offset(@area.height,column_number_start), @area.excel_start.offset(@area.height,column_number_finish)
30
30
 
@@ -32,24 +32,24 @@ class Table
32
32
  r = Reference.for(calling_cell)
33
33
  r.calculate_excel_variables
34
34
  row = r.excel_row_number
35
- column_number_start = @column_name_array.find_index($1.strip.downcase)
36
- column_number_finish = @column_name_array.find_index($2.strip.downcase)
35
+ column_number_start = column_number_for($1)
36
+ column_number_finish = column_number_for($2)
37
37
  return ref_error unless column_number_start && column_number_finish
38
38
  ast_for_area @area.excel_start.offset(row - @area.excel_start.excel_row_number,column_number_start), @area.excel_start.offset(row - @area.excel_start.excel_row_number,column_number_finish)
39
39
 
40
40
  when /\[(.*?)\]:\[(.*?)\]/io
41
- column_number_start = @column_name_array.find_index($1.strip.downcase)
42
- column_number_finish = @column_name_array.find_index($2.strip.downcase)
41
+ column_number_start = column_number_for($1)
42
+ column_number_finish = column_number_for($2)
43
43
  return ref_error unless column_number_start && column_number_finish
44
44
  ast_for_area @area.excel_start.offset(1,column_number_start), @area.excel_start.offset(@area.height - @number_of_total_rows,column_number_finish)
45
45
 
46
46
  when /\[#Headers\],\[(.*?)\]/io
47
- column_number = @column_name_array.find_index($1.strip.downcase)
47
+ column_number = column_number_for($1)
48
48
  return ref_error unless column_number
49
49
  ast_for_cell @area.excel_start.offset(0,column_number)
50
50
 
51
51
  when /\[#Totals\],\[(.*?)\]/io
52
- column_number = @column_name_array.find_index($1.strip.downcase)
52
+ column_number = column_number_for($1)
53
53
  return ref_error unless column_number
54
54
  ast_for_cell @area.excel_start.offset(@area.height,column_number)
55
55
 
@@ -57,7 +57,7 @@ class Table
57
57
  r = Reference.for(calling_cell)
58
58
  r.calculate_excel_variables
59
59
  row = r.excel_row_number
60
- column_number = @column_name_array.find_index($1.strip.downcase)
60
+ column_number = column_number_for($1)
61
61
  return ref_error unless column_number
62
62
  ast_for_cell @area.excel_start.offset(row - @area.excel_start.excel_row_number,column_number)
63
63
 
@@ -96,11 +96,11 @@ class Table
96
96
  r = Reference.for(calling_cell)
97
97
  r.calculate_excel_variables
98
98
  row = r.excel_row_number
99
- column_number = @column_name_array.find_index(structured_reference.strip.downcase)
99
+ column_number = column_number_for(structured_reference)
100
100
  return ref_error unless column_number
101
101
  ast_for_cell @area.excel_start.offset(row - @area.excel_start.excel_row_number,column_number)
102
102
  else
103
- column_number = @column_name_array.find_index(structured_reference.strip.downcase)
103
+ column_number = column_number_for(structured_reference)
104
104
  return ref_error unless column_number
105
105
  ast_for_area @area.excel_start.offset(1,column_number), @area.excel_start.offset(@area.height - @number_of_total_rows,column_number)
106
106
  end
@@ -118,6 +118,11 @@ class Table
118
118
  def ref_error
119
119
  [:error,"#REF!"]
120
120
  end
121
+
122
+ def column_number_for(name)
123
+ name = name.strip.downcase.gsub("'#","#")
124
+ @column_name_array.find_index(name)
125
+ end
121
126
 
122
127
  def includes?(sheet,reference)
123
128
  return false unless @worksheet == sheet
data/src/excel_to_code.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class ExcelToCode
2
- def self.version() "0.2.18" end
2
+ def self.version() "0.2.19" end
3
3
  end
4
4
 
5
5
  require_relative 'commands'
@@ -90,7 +90,12 @@ class CachingFormulaParser
90
90
  end
91
91
 
92
92
  def percentage(ast)
93
- ast[1] = ast[1].to_f
93
+ if ast[1].is_a?(Array)
94
+ ast.replace([:arithmetic, map(ast[1]), operator([:operator, :'/']), number([:number, 100])])
95
+ else
96
+ ast[1] = ast[1].to_f / 100.0
97
+ ast[0] = :number
98
+ end
94
99
  @percentage_cache[ast] ||= ast
95
100
  end
96
101
 
@@ -4,11 +4,16 @@ class EmergencyArrayFormulaReplaceIndirectBodge
4
4
 
5
5
  attr_accessor :current_sheet_name
6
6
  attr_accessor :references
7
+ attr_accessor :named_references
8
+ attr_accessor :tables
9
+ attr_accessor :referring_cell
7
10
 
8
11
  def initialize
9
12
  @indirect_replacer = ReplaceIndirectsWithReferencesAst.new
10
13
  @formulae_to_value_replacer = MapFormulaeToValues.new
11
14
  @inline_formulae_replacer = InlineFormulaeAst.new
15
+ @simplify_arithmetic_replacer ||= SimplifyArithmeticAst.new
16
+ @replace_ranges_with_array_literals_replacer ||= ReplaceRangesWithArrayLiteralsAst.new
12
17
  end
13
18
 
14
19
  def replace(ast)
@@ -25,17 +30,36 @@ class EmergencyArrayFormulaReplaceIndirectBodge
25
30
 
26
31
  def function(ast)
27
32
  new_ast = deep_copy(ast)
33
+ @replace_ranges_with_array_literals_replacer.map(new_ast)
28
34
  @inline_formulae_replacer.references = @references
29
35
  @inline_formulae_replacer.current_sheet_name = [@current_sheet_name]
30
36
  @inline_formulae_replacer.map(new_ast)
37
+ @simplify_arithmetic_replacer.map(new_ast)
38
+
39
+ @formulae_to_value_replacer.map(new_ast)
40
+
41
+ @named_reference_replacer ||= ReplaceNamedReferencesAst.new(@named_references)
42
+ @named_reference_replacer.default_sheet_name = @current_sheet_name
43
+ @named_reference_replacer.map(new_ast)
44
+
45
+ @table_reference_replacer ||= ReplaceTableReferenceAst.new(@tables)
46
+ @table_reference_replacer.worksheet = @current_sheet_name
47
+ @table_reference_replacer.referring_cell = @referring_cell
48
+ @table_reference_replacer.map(new_ast)
49
+
50
+ @replace_ranges_with_array_literals_replacer.map(new_ast)
51
+ @inline_formulae_replacer.map(new_ast)
31
52
  @formulae_to_value_replacer.map(new_ast)
53
+
32
54
  @indirect_replacer.replace(new_ast)
55
+
33
56
  ast.replace(new_ast)
34
57
  ast
35
58
  end
36
59
 
37
60
  def deep_copy(ast)
38
61
  return ast if ast.is_a?(Symbol)
62
+ return ast if ast.is_a?(Numeric)
39
63
  return ast.dup unless ast.is_a?(Array)
40
64
  ast.map do |a|
41
65
  deep_copy(a)
@@ -1,5 +1,3 @@
1
- module BlankCell; end
2
-
3
1
  class InlineFormulaeAst
4
2
 
5
3
  attr_accessor :references, :current_sheet_name, :inline_ast
@@ -40,6 +38,7 @@ class InlineFormulaeAst
40
38
 
41
39
  # Should be of the form [:sheet_reference, sheet_name, reference]
42
40
  # FIXME: Can we rely on reference always being a [:cell, ref] at this stage?
41
+ # FIXME: NO! Because they won't be when they are used in EmergencyArrayFormulaReplaceIndirectBodge
43
42
  def sheet_reference(ast)
44
43
  return unless ast[2][0] == :cell
45
44
  sheet = ast[1].to_sym
@@ -63,7 +63,7 @@ class MapFormulaeToValues
63
63
  case operator.last
64
64
  when :+
65
65
  ast.replace(n(right))
66
- when :*, :/, :^
66
+ when :*
67
67
  ast.replace([:number, 0])
68
68
  end
69
69
  elsif r == 0
@@ -140,6 +140,27 @@ class MapFormulaeToValues
140
140
  ast.replace(formula_value( ast[1],*values))
141
141
  end
142
142
 
143
+ def map_if(ast)
144
+ condition_ast = ast[2]
145
+ true_option_ast = ast[3]
146
+ false_option_ast = ast[4] || [:boolean_false]
147
+
148
+ condition_value = value(condition_ast)
149
+ return if condition_value == :not_a_value
150
+
151
+ case condition_value
152
+ when Symbol
153
+ ast.replace(condition_ast)
154
+ when String
155
+ ast.replace([:error, :"#VALUE!"])
156
+ when false, 0
157
+ ast.replace(false_option_ast)
158
+ else
159
+ ast.replace(true_option_ast)
160
+ end
161
+ ast
162
+ end
163
+
143
164
  def map_right(ast)
144
165
  normal_function(ast, "")
145
166
  end
@@ -161,11 +182,38 @@ class MapFormulaeToValues
161
182
  end
162
183
 
163
184
  def map_sumifs(ast)
164
- sum_value = value(ast[2])
165
185
  values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
166
- return if sum_value == :not_a_value
167
186
  return if values.any? { |a| a == :not_a_value }
168
- ast.replace(formula_value( ast[1], sum_value, *values))
187
+ return unless [:sheet_reference, :cell, :area, :array, :number, :string, :boolean_true, :boolean_false].include?(ast[2].first)
188
+ sum_value = value(ast[2])
189
+ if sum_value == :not_a_value
190
+ partially_map_sumifs(ast)
191
+ else
192
+ ast.replace(formula_value( ast[1], sum_value, *values))
193
+ end
194
+ end
195
+
196
+ def partially_map_sumifs(ast)
197
+ values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
198
+ sum_range = []
199
+ if ast[2].first == :array
200
+ ast[2].each do |row|
201
+ next if row.is_a?(Symbol)
202
+ row.each do |cell|
203
+ next if cell.is_a?(Symbol)
204
+ sum_range << cell
205
+ end
206
+ end
207
+ else
208
+ sum_range = [ast[2]]
209
+ end
210
+ sum_range_indexes = 0.upto(sum_range.length-1).to_a
211
+ filtered_range = @calculator._filtered_range(sum_range_indexes, *values)
212
+ if filtered_range.is_a?(Symbol)
213
+ ast.replace(value(filtered_range))
214
+ else
215
+ ast.replace([:function, :SUM, *sum_range.values_at(*filtered_range)])
216
+ end
169
217
  end
170
218
 
171
219
  # [:function, "COUNT", range]
@@ -180,6 +228,7 @@ class MapFormulaeToValues
180
228
  return map_index_with_only_two_arguments(ast) if ast.length == 4
181
229
 
182
230
  array_mapped = ast[2]
231
+ return ast.replace(ast[2]) if ast[2].first == :error
183
232
  row_as_number = value(ast[3])
184
233
  column_as_number = value(ast[4])
185
234
 
@@ -198,6 +247,7 @@ class MapFormulaeToValues
198
247
  # [:function, "INDEX", array, row_number]
199
248
  def map_index_with_only_two_arguments(ast)
200
249
  array_mapped = ast[2]
250
+ return ast.replace(ast[2]) if ast[2].first == :error
201
251
  row_as_number = value(ast[3])
202
252
  return if row_as_number == :not_a_value
203
253
  array_as_values = array_as_values(array_mapped)
@@ -227,9 +277,10 @@ class MapFormulaeToValues
227
277
  ast.replace([:number, number_total])
228
278
  # FIXME: Will I be haunted by this? What if doing a sum of something that isn't a number
229
279
  # and so what is expected is a VALUE error?. YES. This doesn't work well.
230
- #elsif number_total == 0 && not_number_array.size == 1
231
- # p not_number_array[0]
232
- # ast.replace(not_number_array[0])
280
+ elsif ast.length == 3 && [:cell, :sheet_reference].include?(ast[2].first)
281
+ ast.replace(n(ast[2]))
282
+ elsif ast.length == 3 && ast[2][0] == :function && ast[2][1] == :ENSURE_IS_NUMBER
283
+ ast.replace(ast[2])
233
284
  else
234
285
  new_ast = [:function, :SUM].concat(not_number_array)
235
286
  new_ast.push([:number, number_total]) unless number_total == 0
@@ -243,13 +294,30 @@ class MapFormulaeToValues
243
294
  not_number_array = []
244
295
  case ast.first
245
296
  when :array
297
+ array_total = 0
298
+ array_not_numbers = []
299
+ # First we just have a go at splitting the array into a list of numbers
300
+ # and not numbers.
246
301
  array_as_values(ast).each do |row|
247
302
  row.each do |c|
248
303
  result = filter_numbers_and_not(c)
249
- number_total += result.first
250
- not_number_array.concat(result.last)
304
+ array_total += result.first
305
+ array_not_numbers.concat(result.last)
251
306
  end
252
307
  end
308
+ # If there are no not_numbers, or only one, we are good
309
+ if array_not_numbers.length <= 1
310
+ number_total += array_total
311
+ not_number_array.concat(array_not_numbers)
312
+ # If there are more than on not_numbers we aren't neccessarily good
313
+ # unless all those not numbers are simple
314
+ elsif array_not_numbers.all? { |c| [:cell, :area, :sheet_reference].include?(c.first)}
315
+ number_total += array_total
316
+ not_number_array.concat(array_not_numbers)
317
+ # Otherwise, leave that array alone
318
+ else
319
+ not_number_array.push(ast)
320
+ end
253
321
  when :blank, :number, :percentage, :string, :boolean_true, :boolean_false
254
322
  number = @calculator.number_argument(value(ast))
255
323
  if number.is_a?(Symbol)
@@ -14,7 +14,6 @@ class ReplaceArithmeticOnRangesAst
14
14
  # Three different options, array on the left, array on the right, or both
15
15
  # array on the left first
16
16
  if left.first == :array && right.first != :array
17
- map(right)
18
17
  ast.replace(
19
18
  array_map(left) do |cell|
20
19
  [:comparison, map(cell), operator, right]
@@ -23,7 +22,6 @@ class ReplaceArithmeticOnRangesAst
23
22
 
24
23
  # array on the right next
25
24
  elsif left.first != :array && right.first == :array
26
- map(left)
27
25
  ast.replace(
28
26
  array_map(right) do |cell|
29
27
  [:comparison, left, operator, map(cell)]
@@ -49,9 +47,6 @@ class ReplaceArithmeticOnRangesAst
49
47
  end
50
48
  end
51
49
  )
52
- else
53
- map(left)
54
- map(right)
55
50
  end
56
51
  end
57
52
 
@@ -64,7 +59,6 @@ class ReplaceArithmeticOnRangesAst
64
59
  # Three different options, array on the left, array on the right, or both
65
60
  # array on the left first
66
61
  if left.first == :array && right.first != :array
67
- map(right)
68
62
  ast.replace(
69
63
  array_map(left) do |cell|
70
64
  [:arithmetic, map(cell), operator, right]
@@ -73,7 +67,6 @@ class ReplaceArithmeticOnRangesAst
73
67
 
74
68
  # array on the right next
75
69
  elsif left.first != :array && right.first == :array
76
- map(left)
77
70
  ast.replace(
78
71
  array_map(right) do |cell|
79
72
  [:arithmetic, left, operator, map(cell)]
@@ -99,9 +92,6 @@ class ReplaceArithmeticOnRangesAst
99
92
  end
100
93
  end
101
94
  )
102
- else
103
- map(left)
104
- map(right)
105
95
  end
106
96
  end
107
97
 
@@ -18,10 +18,13 @@ class ReplaceArraysWithSingleCellsAst
18
18
  # Special case, only change if at the top level
19
19
  elsif ast[0] == :function && ast[1] == :CHOOSE && check_choose(ast)
20
20
  # Replacement made in check
21
+ # Special case, only change if at the top level
22
+ elsif ast[0] == :function && ast[1] == :IF && check_if(ast)
23
+ # Replacement made in check
21
24
  else
22
25
  do_map(ast)
23
- ast
24
26
  end
27
+ ast
25
28
  end
26
29
 
27
30
  def do_map(ast)
@@ -74,6 +77,19 @@ class ReplaceArraysWithSingleCellsAst
74
77
  replacement_made
75
78
  end
76
79
 
80
+ def check_if(ast)
81
+ replacement_made = false
82
+ if ast[3] && ast[3].first == :array
83
+ replacement_made = true
84
+ ast[3] = try_and_convert_array(ast[3])
85
+ end
86
+ if ast[4] && ast[4].first == :array
87
+ replacement_made = true
88
+ ast[4] = try_and_convert_array(ast[3])
89
+ end
90
+ replacement_made
91
+ end
92
+
77
93
  def check_match(ast)
78
94
  return false unless ast[2].first == :array
79
95
  ast[2] = try_and_convert_array(ast[2])
@@ -10,18 +10,32 @@ class MapValuesToConstants
10
10
  end
11
11
  end
12
12
 
13
- POTENTIAL_CONSTANTS = { :number => true, :percentage => true, :string => true}
14
-
15
13
  def map(ast)
16
14
  return ast unless ast.is_a?(Array)
17
15
  operator = ast[0]
18
- if POTENTIAL_CONSTANTS.has_key?(operator)
16
+ if replace?(ast)
19
17
  ast.replace([:constant, constants[ast.dup]])
20
18
  else
21
19
  ast.each { |a| map(a) }
22
20
  end
23
21
  end
24
22
 
23
+ def replace?(ast)
24
+ case ast.first
25
+ when :string; return true
26
+ when :percentage, :number
27
+ n = ast.last.to_f
28
+ # Don't use constant if an integer less than ten,
29
+ # as will use constant predefined in the c runtime
30
+ return true if n > 10
31
+ return true if n < 0
32
+ return false if n % 1 == 0
33
+ return true
34
+ else
35
+ return false
36
+ end
37
+ end
38
+
25
39
  end
26
40
 
27
41
 
@@ -9,7 +9,9 @@ class SimplifyArithmeticAst
9
9
 
10
10
  def simplify_arithmetic(ast)
11
11
  return ast unless ast.is_a?(Array)
12
- ast.each { |a| simplify_arithmetic(a) }
12
+ ast.each do |a|
13
+ simplify_arithmetic(a) if a.is_a?(Array)
14
+ end
13
15
  case ast[0]
14
16
  when :arithmetic; arithmetic(ast)
15
17
  when :brackets; @brackets_to_remove << ast
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excel_to_code
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.18
4
+ version: 0.2.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Counsell, Green on Black Ltd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-02 00:00:00.000000000 Z
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubypeg
@@ -277,7 +277,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
277
277
  version: '0'
278
278
  requirements: []
279
279
  rubyforge_project:
280
- rubygems_version: 2.2.0
280
+ rubygems_version: 2.2.2
281
281
  signing_key:
282
282
  specification_version: 4
283
283
  summary: Converts .xlxs files into pure ruby 1.9 code or pure C code so that they