excel_to_code 0.2.14 → 0.2.15

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