excel_to_code 0.2.14 → 0.2.15

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: 5462c5420b1ec9bd0cb8fc6c2a6641475348f7b1
4
- data.tar.gz: e5bd8eb39e00007be6ac7b847d7b3fabd3260dcf
3
+ metadata.gz: f02167c81fefd0532b50c1fc9c9014a4398306f5
4
+ data.tar.gz: ec0ce7e9de3c7ed214052a9a45bad36c02e1c763
5
5
  SHA512:
6
- metadata.gz: 49e9dde17bb074100d96a2868a79f95bf1ec111dfe0bf6675759a272407980cd830110a7eda9db3c5257d2bb2ead0d889798b564fe0a82fcddc87d757298d6e3
7
- data.tar.gz: 2e4da6cd7577fc93718190048457e08fcd0067a66d69ac45fdfff46b99622b58c20ae22591d0e76d99e40670cd8f2ded881f7dc6b544fcd447abb9e61c64357e
6
+ metadata.gz: 52fd6998dddf65a826c6bb3a014d5b915342845ab5a1801dfc3c4fc7572f446aedd2f8662c92aa186be8c0b5dd9719ffcc2f90f1b71842df6cb5c97e886cbc8f
7
+ data.tar.gz: 7725a362a6049b3533e1db2c1be5c256581fb915519d274b89d3c9790f83e654d9470deb6c9148549fe905cf51e3ff6ee35794f6d4026feb357d23e2c07a1d74
@@ -98,6 +98,11 @@ class ExcelToX
98
98
  # * false - empty cells and zeros are treated as being different in tests. Numbers must match to full accuracy.
99
99
  attr_accessor :sloppy_tests
100
100
 
101
+ # Optional attribute, Boolean. Default true
102
+ # * true - the compiler attempts to extract bits of calculation that appear in more than one formula into separate methods. This should increase performance
103
+ # * false - the compiler leaves calculations fully expanded. This may make debugging easier
104
+ attr_accessor :extract_repeated_parts_of_formulae
105
+
101
106
  # Deprecated
102
107
  def run_in_memory=(boolean)
103
108
  $stderr.puts "The run_in_memory switch is deprecated (it is now always true). Please remove calls to it"
@@ -179,7 +184,7 @@ class ExcelToX
179
184
  replace_formulae_with_their_results
180
185
  inline_formulae_that_are_only_used_once
181
186
  remove_any_cells_not_needed_for_outputs
182
- separate_formulae_elements
187
+ separate_formulae_elements if extract_repeated_parts_of_formulae
183
188
  replace_values_with_constants
184
189
  create_sorted_references_to_test
185
190
 
@@ -213,6 +218,9 @@ class ExcelToX
213
218
 
214
219
  # 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)
215
220
  self.sloppy_tests ||= true
221
+
222
+ # Setting this to false may make it easier to figure out errors
223
+ self.extract_repeated_parts_of_formulae = true if @extract_repeated_parts_of_formulae == nil
216
224
  end
217
225
 
218
226
 
@@ -811,21 +819,31 @@ class ExcelToX
811
819
  @sheetless_cell_reference_replacer ||= RewriteCellReferencesToIncludeSheetAst.new
812
820
 
813
821
  cells.each do |ref, ast|
814
- @sheetless_cell_reference_replacer.worksheet = ref.first
815
- @sheetless_cell_reference_replacer.map(ast)
816
- @shared_string_replacer.map(ast)
817
- @named_reference_replacer.default_sheet_name = ref.first
818
- @named_reference_replacer.map(ast)
819
- @table_reference_replacer.worksheet = ref.first
820
- @table_reference_replacer.referring_cell = ref.last
821
- @table_reference_replacer.map(ast)
822
- @replace_ranges_with_array_literals_replacer.map(ast)
823
- @replace_arithmetic_on_ranges_replacer.map(ast)
824
- @replace_arrays_with_single_cells_replacer.map(ast)
825
- @replace_string_joins_on_ranges_replacer.map(ast)
826
- @wrap_formulae_that_return_arrays_replacer.map(ast)
827
- end
822
+ begin
823
+ @sheetless_cell_reference_replacer.worksheet = ref.first
824
+ @sheetless_cell_reference_replacer.map(ast)
825
+ @shared_string_replacer.map(ast)
826
+ @named_reference_replacer.default_sheet_name = ref.first
827
+ @named_reference_replacer.map(ast)
828
+ @table_reference_replacer.worksheet = ref.first
829
+ @table_reference_replacer.referring_cell = ref.last
830
+ @table_reference_replacer.map(ast)
831
+ @replace_ranges_with_array_literals_replacer.map(ast)
832
+ #@replace_arithmetic_on_ranges_replacer.map(ast)
833
+
834
+ @replace_arrays_with_single_cells_replacer.ref = ref
835
+ a = @replace_arrays_with_single_cells_replacer.map(ast)
836
+ if @replace_arrays_with_single_cells_replacer.need_to_replace
837
+ cells[ref] = a
838
+ end
828
839
 
840
+ #@replace_string_joins_on_ranges_replacer.map(ast)
841
+ @wrap_formulae_that_return_arrays_replacer.map(ast)
842
+ rescue Exception => e
843
+ log.fatal "Exception when simplifying #{ref}: #{ast}"
844
+ raise
845
+ end
846
+ end
829
847
  end
830
848
 
831
849
  # These types of cells don't conatain formulae and can therefore be skipped
@@ -148,6 +148,45 @@ class MapFormulaeToC < MapValuesToC
148
148
  "#{FUNCTIONS[:AVERAGEIFS]}(#{map(average_range)}, #{map_arguments_to_array(criteria)})"
149
149
  end
150
150
 
151
+ def function_if(condition, true_case, false_case = [:boolean_false])
152
+ true_code = map(true_case)
153
+ false_code = map(false_case)
154
+
155
+ condition_name = "condition#{@counter}"
156
+ result_name = "ifresult#{@counter}"
157
+ @counter += 1
158
+
159
+ initializers << "ExcelValue #{condition_name} = #{map(condition)};"
160
+ initializers << "ExcelValue #{result_name};"
161
+ initializers << "switch(#{condition_name}.type) {"
162
+ initializers << "case ExcelBoolean:"
163
+ initializers << " if(#{condition_name}.number == true) {"
164
+ initializers << " #{result_name} = #{true_code};"
165
+ initializers << " } else {"
166
+ initializers << " #{result_name} = #{false_code};"
167
+ initializers << " }"
168
+ initializers << " break;"
169
+ initializers << "case ExcelNumber:"
170
+ initializers << " if(#{condition_name}.number == 0) {"
171
+ initializers << " #{result_name} = #{false_code};"
172
+ initializers << " } else {"
173
+ initializers << " #{result_name} = #{true_code};"
174
+ initializers << " }"
175
+ initializers << " break;"
176
+ initializers << "case ExcelEmpty: "
177
+ initializers << " #{result_name} = #{false_code};"
178
+ initializers << " break;"
179
+ initializers << "case ExcelString:"
180
+ initializers << "case ExcelRange:"
181
+ initializers << " #{result_name} = VALUE;"
182
+ initializers << " break;"
183
+ initializers << "case ExcelError:"
184
+ initializers << " #{result_name} = #{condition_name};"
185
+ initializers << " break;"
186
+ initializers << "}"
187
+
188
+ return result_name
189
+ end
151
190
 
152
191
  def any_number_of_argument_function(function_name,arguments)
153
192
  "#{FUNCTIONS[function_name.to_sym]}(#{map_arguments_to_array(arguments)})"
data/src/excel_to_code.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class ExcelToCode
2
- def self.version() "0.2.14" end
2
+ def self.version() "0.2.15" end
3
3
  end
4
4
 
5
5
  require_relative 'commands'
@@ -2,32 +2,98 @@ require_relative '../excel'
2
2
 
3
3
  class ReplaceArraysWithSingleCellsAst
4
4
 
5
+ attr_accessor :ref
6
+ attr_accessor :need_to_replace
7
+
8
+ ERROR = [:error, :"#VALUE!"]
9
+
5
10
  def map(ast)
6
- return ast unless ast.first == :array
7
- ast[1][1]
11
+ @need_to_replace = false
12
+ return unless ast.is_a?(Array)
13
+ if ast.first == :array
14
+ @need_to_replace = true
15
+ return try_and_convert_array(ast)
16
+ else
17
+ do_map(ast)
18
+ ast
19
+ end
8
20
  end
9
- end
10
21
 
22
+ def do_map(ast)
23
+ return ast unless ast.is_a?(Array)
24
+ case ast.first
25
+ when :arithmetic
26
+ left, op, right = ast[1], ast[2], ast[3]
27
+ if left.first == :array || right.first == :array
28
+ left = try_and_convert_array(left)
29
+ right = try_and_convert_array(right)
30
+ ast.replace([:arithmetic, left, op, right])
31
+ else
32
+ ast[1..-1].each { |a| do_map(a) }
33
+ end
34
+ when :string_join
35
+ strings = ast[1..-1]
36
+ if strings.any? { |s| s.first == :array }
37
+ strings = strings.map { |s| try_and_convert_array(s) }
38
+ ast.replace([:string_join, *strings])
39
+ else
40
+ ast[1..-1].each { |a| do_map(a) }
41
+ end
42
+ else
43
+ ast[1..-1].each { |a| do_map(a) }
44
+ end
45
+ end
11
46
 
12
- class ReplaceArraysWithSingleCells
13
-
14
- def self.replace(*args)
15
- self.new.replace(*args)
47
+ def try_and_convert_array(ast)
48
+ return ast unless ast.first == :array
49
+ return ast unless all_references?(ast)
50
+ #return ast unless ast[1..-1].all? { |c| c.first == :sheet_reference }
51
+ if ast.length == 2
52
+ single_row(ast)
53
+ elsif ast[1].length == 2
54
+ single_column(ast)
55
+ else
56
+ ERROR
57
+ end
16
58
  end
17
-
18
- def replace(input,output)
19
-
20
- replacer = ReplaceArraysWithSingleCellsAst.new
21
- input.each_line do |line|
22
- # Looks to match shared string lines
23
- if line =~ /\[:array/
24
- content = line.split("\t")
25
- ast = eval(content.pop)
26
- output.puts "#{content.join("\t")}\t#{replacer.map(ast).inspect}"
27
- else
28
- output.puts line
59
+
60
+ def all_references?(ast)
61
+ ast[1..-1].all? do |row|
62
+ row[1..-1].all? do |cell|
63
+ cell.first == :sheet_reference
29
64
  end
30
65
  end
31
66
  end
32
-
67
+
68
+ def single_row(ast)
69
+ r = Reference.for(ref.last)
70
+ r.calculate_excel_variables
71
+ column = r.excel_column
72
+ sheet = ref.first
73
+
74
+ cells = ast[1][1..-1]
75
+ match = cells.find do |cell|
76
+ s = cell[1]
77
+ c = cell[2][1][/([A-Za-z]{1,3})/,1]
78
+ sheet == s && column == c
79
+ end
80
+
81
+ match || ERROR
82
+ end
83
+
84
+ def single_column(ast)
85
+ r = Reference.for(ref.last)
86
+ r.calculate_excel_variables
87
+ row = r.excel_row
88
+ sheet = ref.first
89
+
90
+ cells = ast[1..-1].map { |row| row.last }
91
+ match = cells.find do |cell|
92
+ s = cell[1]
93
+ r = cell[2][1][/([A-Za-z]{1,3})(\d+)/,2]
94
+ sheet == s && row == r
95
+ end
96
+
97
+ match || ERROR
98
+ end
33
99
  end
@@ -29,6 +29,11 @@ class ReplaceOffsetsWithReferencesAst
29
29
  column_offset = ast[4]
30
30
  height = ast[5] || [:number, 1]
31
31
  width = ast[6] || [:number, 1]
32
+ [row_offset, column_offset, height, width].each do |arg|
33
+ next unless arg.first == :error
34
+ ast.replace(arg)
35
+ return
36
+ end
32
37
  return unless [row_offset, column_offset, height, width].all? { |a| a.first == :number }
33
38
  if reference.first == :cell
34
39
  ast.replace(offset_cell(reference, row_offset, column_offset, height, width))
@@ -16,19 +16,69 @@ class ReplaceRangesWithArrayLiteralsAst
16
16
  case ast[0]
17
17
  when :sheet_reference; return sheet_reference(ast)
18
18
  when :area; return area(ast)
19
+ when :function;
20
+ if ast[1] == :SUMIF
21
+ return sumif(ast)
22
+ else
23
+ map_args(ast)
24
+ end
19
25
  else
20
- ast.each.with_index do |a,i|
21
- next unless a.is_a?(Array)
22
- case a[0]
23
- when :sheet_reference; ast[i] = sheet_reference(a)
24
- when :area; ast[i] = area(a)
25
- else do_map(a)
26
+ map_args(ast)
27
+ end
28
+ ast
29
+ end
30
+
31
+ def map_args(ast)
32
+ ast.each.with_index do |a,i|
33
+ next unless a.is_a?(Array)
34
+ case a[0]
35
+ when :sheet_reference; ast[i] = sheet_reference(a)
36
+ when :area; ast[i] = area(a)
37
+ when :function
38
+ if ast[1] == :sumif
39
+ ast[i] = sumif(a)
40
+ else
41
+ do_map(a)
26
42
  end
43
+ else
44
+ do_map(a)
27
45
  end
28
46
  end
29
- ast
30
47
  end
31
-
48
+
49
+ # ARGH: SUMIF(A1:A10, 10, B5:B6) is interpreted by Excel as SUMIF(A1:A10, 10, B5:B15)
50
+ def sumif(ast)
51
+ # If only two arguments to SUMIF, won't be a problem
52
+ return map_args(ast) unless ast.length == 5
53
+ check_range, criteria, sum_range = ast[2], ast[3], ast[4]
54
+ return map_args(ast) unless [:area, :sheet_reference, :cell].include?(check_range.first)
55
+ return map_args(ast) unless [:area, :sheet_reference, :cell].include?(sum_range.first)
56
+ check_area = area_for(check_range)
57
+ sum_area = area_for(sum_range)
58
+
59
+ check_area.calculate_excel_variables
60
+ sum_area.calculate_excel_variables
61
+
62
+ return map_args(ast) if check_area.height == sum_area.height && check_area.width == sum_area.width
63
+
64
+ new_sum_area = [:area, sum_area.excel_start.to_sym, sum_area.excel_start.offset(check_area.height, check_area.width).to_sym]
65
+
66
+ if sum_range.first == :sheet_reference
67
+ ast[4][2] = new_sum_area
68
+ else
69
+ ast[4] = new_sum_area
70
+ end
71
+ map_args(ast)
72
+ end
73
+
74
+ def area_for(ast)
75
+ case ast.first
76
+ when :cell then Area.for(ast[1])
77
+ when :area then Area.for("#{ast[1]}:#{ast[2]}")
78
+ when :sheet_reference then area_for(ast[2])
79
+ end
80
+ end
81
+
32
82
  # Of the form [:sheet_reference, sheet, reference]
33
83
  def sheet_reference(ast)
34
84
  @cache[ast] || calculate_expansion_for(ast)
@@ -40,16 +90,16 @@ class ReplaceRangesWithArrayLiteralsAst
40
90
  return ast unless reference.first == :area
41
91
  area = Area.for("#{reference[1]}:#{reference[2]}")
42
92
  a = area.to_array_literal(sheet)
43
-
93
+
44
94
  # Don't convert single cell ranges
45
95
  result = if a.size == 2 && a[1].size == 2
46
- a[1][1]
47
- else
48
- a
49
- end
96
+ a[1][1]
97
+ else
98
+ a
99
+ end
50
100
  @cache[ast.dup] = result
51
101
  end
52
-
102
+
53
103
  # Of the form [:area, start, finish]
54
104
  def area(ast)
55
105
  start = ast[1]
@@ -64,18 +114,18 @@ class ReplaceRangesWithArrayLiteralsAst
64
114
  a
65
115
  end
66
116
  end
67
-
117
+
68
118
  end
69
119
 
70
120
  class ReplaceRangesWithArrayLiterals
71
-
121
+
72
122
  def self.replace(*args)
73
123
  self.new.replace(*args)
74
124
  end
75
-
125
+
76
126
  def replace(input,output)
77
127
  rewriter = ReplaceRangesWithArrayLiteralsAst.new
78
-
128
+
79
129
  input.each_line do |line|
80
130
  # Looks to match shared string lines
81
131
  if line =~ /\[:area/
@@ -87,5 +137,5 @@ class ReplaceRangesWithArrayLiterals
87
137
  end
88
138
  end
89
139
  end
90
-
140
+
91
141
  end
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.14
4
+ version: 0.2.15
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-02-04 00:00:00.000000000 Z
11
+ date: 2014-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubypeg