excel_to_code 0.2.15 → 0.2.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,6 +36,7 @@ class MapFormulaeToC < MapValuesToC
36
36
  :'COSH' => 'cosh',
37
37
  :'COUNT' => 'count',
38
38
  :'COUNTA' => 'counta',
39
+ :'ENSURE_IS_NUMBER' => 'ensure_is_number',
39
40
  :'EXP' => 'excel_exp',
40
41
  :'FIND2' => 'find_2',
41
42
  :'FIND3' => 'find',
@@ -53,6 +54,7 @@ class MapFormulaeToC < MapValuesToC
53
54
  :'LARGE' => 'large',
54
55
  :'LEFT1' => 'left_1',
55
56
  :'LEFT2' => 'left',
57
+ :'LEN' => 'len',
56
58
  :'LOG1' => 'excel_log',
57
59
  :'LOG2' => 'excel_log_2',
58
60
  :'MATCH2' => 'excel_match_2',
@@ -67,6 +69,8 @@ class MapFormulaeToC < MapValuesToC
67
69
  :'PV5' => 'pv_5',
68
70
  :'RANK2' => 'rank_2',
69
71
  :'RANK3' => 'rank',
72
+ :'RIGHT1' => 'right_1',
73
+ :'RIGHT2' => 'right',
70
74
  :'ROUND' => 'excel_round',
71
75
  :'ROUNDDOWN' => 'rounddown',
72
76
  :'ROUNDUP' => 'roundup',
@@ -18,6 +18,10 @@ class MapValuesToC
18
18
  def blank
19
19
  "BLANK"
20
20
  end
21
+
22
+ def inlined_blank
23
+ "ZERO"
24
+ end
21
25
 
22
26
  def constant(name)
23
27
  name
@@ -26,6 +26,7 @@ class MapFormulaeToRuby < MapValuesToRuby
26
26
  :'COSH' => 'cosh',
27
27
  :'COUNT' => 'count',
28
28
  :'COUNTA' => 'counta',
29
+ :'ENSURE_IS_NUMBER' => 'ensure_is_number',
29
30
  :'EXP' => 'exp',
30
31
  :'FIND' => 'find',
31
32
  :'FORECAST' => 'forecast',
@@ -82,7 +83,12 @@ class MapFormulaeToRuby < MapValuesToRuby
82
83
  end
83
84
 
84
85
  def string_join(*strings)
85
- "string_join(#{strings.map {|a| map(a)}.join(',')})"
86
+ strings = strings.map do |s|
87
+ s = [:string, ""] if s == [:inlined_blank]
88
+ s = map(s)
89
+ end
90
+
91
+ "string_join(#{strings.join(',')})"
86
92
  end
87
93
 
88
94
  def comparison(left,operator,right)
@@ -20,6 +20,10 @@ class MapValuesToRuby
20
20
  def blank
21
21
  "nil"
22
22
  end
23
+
24
+ def inlined_blank
25
+ "0.0"
26
+ end
23
27
 
24
28
  def constant(constant)
25
29
  map(constants[constant])
data/src/excel/area.rb CHANGED
@@ -14,9 +14,13 @@ class Area < String
14
14
  end
15
15
 
16
16
  attr_reader :excel_start, :excel_finish
17
+
18
+ def unfix
19
+ calculate_excel_variables
20
+ Area.for("#{@excel_start.unfix}:#{@excel_finish.unfix}").calculate_excel_variables
21
+ end
17
22
 
18
23
  def calculate_excel_variables
19
- return if @excel_variables_calculated
20
24
  if self =~ /([^:]+):(.*)/
21
25
  @excel_start = Reference.for($1)
22
26
  @excel_finish = Reference.for($2)
@@ -25,6 +29,7 @@ class Area < String
25
29
  end
26
30
  @excel_start.calculate_excel_variables
27
31
  @excel_finish.calculate_excel_variables
32
+ self
28
33
  end
29
34
 
30
35
  def offset(row,column)
@@ -104,3 +104,5 @@ require_relative 'excel_functions/isblank'
104
104
  require_relative 'excel_functions/averageifs'
105
105
 
106
106
  require_relative 'excel_functions/forecast'
107
+
108
+ require_relative 'excel_functions/ensure_is_number'
@@ -0,0 +1,7 @@
1
+ module ExcelFunctions
2
+
3
+ def ensure_is_number(a)
4
+ return number_argument(a)
5
+ end
6
+
7
+ end
@@ -2,6 +2,7 @@ module ExcelFunctions
2
2
 
3
3
  def excel_if(condition,true_value,false_value = false)
4
4
  return condition if condition.is_a?(Symbol)
5
+ return false_value if condition == 0
5
6
  condition ? true_value : false_value
6
7
  end
7
8
 
@@ -4,6 +4,7 @@ module ExcelFunctions
4
4
  return string if string.is_a?(Symbol)
5
5
  return characters if characters.is_a?(Symbol)
6
6
  return nil if string == nil || characters == nil
7
+ return :value if characters < 0
7
8
  string = "TRUE" if string == true
8
9
  string = "FALSE" if string == false
9
10
  string.to_s.slice(0,characters)
@@ -4,10 +4,11 @@ module ExcelFunctions
4
4
  return string if string.is_a?(Symbol)
5
5
  return characters if characters.is_a?(Symbol)
6
6
  return nil if string == nil || characters == nil
7
+ return :value if characters < 0
7
8
  string = "TRUE" if string == true
8
9
  string = "FALSE" if string == false
9
10
  string = string.to_s
10
- string.slice(string.length-characters,characters)
11
+ string.slice(string.length-characters,characters) || ""
11
12
  end
12
13
 
13
14
  end
@@ -67,7 +67,7 @@ class Formula < RubyPeg
67
67
 
68
68
  def comparison
69
69
  node :comparison do
70
- (arithmetic || thing) && space && comparator && space && (arithmetic || thing)
70
+ (arithmetic || string_join || thing) && space && comparator && space && (arithmetic || string_join || thing)
71
71
  end
72
72
  end
73
73
 
@@ -111,7 +111,7 @@ class Formula < RubyPeg
111
111
 
112
112
  def external_reference
113
113
  node :external_reference do
114
- terminal(/\[\d+\]!?/) && any_internal_reference
114
+ terminal(/\[\d+\]!?/) && (any_internal_reference || named_reference)
115
115
  end
116
116
  end
117
117
 
@@ -9,7 +9,7 @@ row := basic_type ( space `',' space basic_type )*
9
9
  basic_type = string | percentage | number | boolean
10
10
  string_join := (arithmetic | thing) (space `"&" space (arithmetic | thing))+
11
11
  arithmetic := thing (space operator space thing)+
12
- comparison := (arithmetic | thing) space comparator space (arithmetic | thing)
12
+ comparison := (arithmetic | string_join | thing) space comparator space (arithmetic | string_join | thing)
13
13
  comparator := '>=' | '<=' | '<>' | '>' | '<' | '='
14
14
  string := `'"' /(""|[^"])*/ `'"'
15
15
  any_reference = external_reference | any_internal_reference
@@ -17,7 +17,7 @@ any_internal_reference = table_reference | local_table_reference | she
17
17
  percentage := /[-+]?[0-9]+\.?[0-9]*/ `'%'
18
18
  number := /[-+]?[0-9]+\.?[0-9]*([eE][-+]?[0-9]+)?/
19
19
  operator := '+' | '-' | '/' | '*' | '^'
20
- external_reference := /\[\d+\]!?/ any_internal_reference
20
+ external_reference := /\[\d+\]!?/ (any_internal_reference | named_reference)
21
21
  table_reference := table_name `'[' (range_structured_reference | short_range_structured_reference | complex_structured_reference | overly_structured_reference | simple_structured_reference) `']'
22
22
  local_table_reference := `'[' (range_structured_reference | short_range_structured_reference | complex_structured_reference | overly_structured_reference | simple_structured_reference) `']'
23
23
  table_name = /[.\p{Word}_]+/
@@ -41,6 +41,7 @@ class Reference < String
41
41
  @excel_row_number = @excel_row.to_i
42
42
  @excel_column_number = @@column_number_for_column[@excel_column]
43
43
  @excel_variables_calculated = true
44
+ self
44
45
  end
45
46
 
46
47
  def offset(rows,columns)
data/src/excel_to_code.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class ExcelToCode
2
- def self.version() "0.2.15" end
2
+ def self.version() "0.2.16" end
3
3
  end
4
4
 
5
5
  require_relative 'commands'
@@ -2,6 +2,8 @@ require 'ox'
2
2
 
3
3
  class ExtractDataFromWorksheet < ::Ox::Sax
4
4
 
5
+ attr_accessor :only_extract_values
6
+
5
7
  attr_accessor :table_rids
6
8
  attr_accessor :worksheets_dimensions
7
9
  attr_accessor :values
@@ -75,6 +77,8 @@ class ExtractDataFromWorksheet < ::Ox::Sax
75
77
  @values[key] = @fp.map(ast)
76
78
  when :f
77
79
  @current_element.pop
80
+ return if only_extract_values
81
+
78
82
  unless @formula.empty?
79
83
  formula_text = @formula.join.gsub(/[\r\n]+/,'')
80
84
  ast = @fp.parse(formula_text)
@@ -1,6 +1,8 @@
1
+ module BlankCell; end
2
+
1
3
  class InlineFormulaeAst
2
4
 
3
- BLANK = [:blank]
5
+ BLANK = [:inlined_blank]
4
6
 
5
7
  attr_accessor :references, :current_sheet_name, :inline_ast
6
8
  attr_accessor :count_replaced
@@ -28,7 +28,7 @@ class MapFormulaeToValues
28
28
  # FIXME: Remove references to this method
29
29
  end
30
30
 
31
- DO_NOT_MAP = {:number => true, :string => true, :blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true, :sheet_reference => true, :cell => true}
31
+ DO_NOT_MAP = {:number => true, :string => true, :blank => true, :inlined_blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true, :sheet_reference => true, :cell => true}
32
32
 
33
33
  def map(ast)
34
34
  ast[1..-1].each do |a|
@@ -62,14 +62,14 @@ class MapFormulaeToValues
62
62
  elsif l == 0
63
63
  case operator.last
64
64
  when :+
65
- ast.replace(right)
65
+ ast.replace(n(right))
66
66
  when :*, :/, :^
67
67
  ast.replace([:number, 0])
68
68
  end
69
69
  elsif r == 0
70
70
  case operator.last
71
71
  when :+, :-
72
- ast.replace(left)
72
+ ast.replace(n(left))
73
73
  when :*
74
74
  ast.replace([:number, 0])
75
75
  when :/
@@ -80,19 +80,24 @@ class MapFormulaeToValues
80
80
  elsif l == 1
81
81
  case operator.last
82
82
  when :*
83
- ast.replace(right)
83
+ ast.replace(n(right))
84
84
  when :^
85
85
  ast.replace([:number, 1])
86
86
  end
87
87
  elsif r == 1
88
88
  case operator.last
89
89
  when :*, :/, :^
90
- ast.replace(left)
90
+ ast.replace(n(left))
91
91
  end
92
92
  end
93
93
  ast
94
94
  end
95
95
 
96
+ def n(ast)
97
+ return ast if ast[0] == :function && ast[1] == :ENSURE_IS_NUMBER
98
+ [:function, :ENSURE_IS_NUMBER, ast]
99
+ end
100
+
96
101
  def comparison(ast)
97
102
  left, operator, right = ast[1], ast[2], ast[3]
98
103
  l = value(left)
@@ -108,7 +113,9 @@ class MapFormulaeToValues
108
113
 
109
114
  # [:string_join, stringA, stringB, ...]
110
115
  def string_join(ast)
111
- values = ast[1..-1].map { |a| value(a) }
116
+ values = ast[1..-1].map do |a|
117
+ value(a, "")
118
+ end
112
119
  return if values.any? { |a| a == :not_a_value }
113
120
  ast.replace(ast_for_value(@calculator.string_join(*values)))
114
121
  end
@@ -123,18 +130,45 @@ class MapFormulaeToValues
123
130
  if respond_to?("map_#{name.to_s.downcase}")
124
131
  send("map_#{name.to_s.downcase}",ast)
125
132
  else
126
- values = ast[2..-1].map { |a| value(a) }
127
- return if values.any? { |a| a == :not_a_value }
128
- ast.replace(formula_value(name,*values))
133
+ normal_function(ast)
129
134
  end
130
135
  end
131
136
 
137
+ def normal_function(ast, inlined_blank = 0)
138
+ values = ast[2..-1].map { |a| value(a, inlined_blank) }
139
+ return if values.any? { |a| a == :not_a_value }
140
+ ast.replace(formula_value( ast[1],*values))
141
+ end
142
+
143
+ def map_right(ast)
144
+ normal_function(ast, "")
145
+ end
146
+
147
+ def map_left(ast)
148
+ normal_function(ast, "")
149
+ end
150
+
151
+ def map_mid(ast)
152
+ normal_function(ast, "")
153
+ end
154
+
155
+ def map_len(ast)
156
+ normal_function(ast, "")
157
+ end
158
+
159
+ def map_sumifs(ast)
160
+ sum_value = value(ast[2])
161
+ values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
162
+ return if sum_value == :not_a_value
163
+ return if values.any? { |a| a == :not_a_value }
164
+ ast.replace(formula_value( ast[1], sum_value, *values))
165
+ end
166
+
132
167
  # [:function, "COUNT", range]
133
168
  def map_count(ast)
134
- range = ast[2]
135
- return unless [:array, :cell, :sheet_reference].include?(range.first)
136
- range = array_as_values(range)
137
- ast.replace(ast_for_value(range.size * range.first.size))
169
+ values = ast[2..-1].map { |a| value(a, nil) }
170
+ return if values.any? { |a| a == :not_a_value }
171
+ ast.replace(formula_value( ast[1],*values))
138
172
  end
139
173
 
140
174
  # [:function, "INDEX", array, row_number, column_number]
@@ -249,10 +283,11 @@ class MapFormulaeToValues
249
283
  :"#NUM!" => :num
250
284
  }
251
285
 
252
- def value(ast)
253
- return extract_values_from_array(ast) if ast.first == :array
286
+ def value(ast, inlined_blank = 0)
287
+ return extract_values_from_array(ast, inlined_blank) if ast.first == :array
254
288
  case ast.first
255
289
  when :blank; nil
290
+ when :inlined_blank; inlined_blank
256
291
  when :null; nil
257
292
  when :number; ast[1]
258
293
  when :percentage; ast[1]/100.0
@@ -264,10 +299,10 @@ class MapFormulaeToValues
264
299
  end
265
300
  end
266
301
 
267
- def extract_values_from_array(ast)
302
+ def extract_values_from_array(ast, inlined_blank = 0)
268
303
  ast[1..-1].map do |row|
269
304
  row[1..-1].map do |cell|
270
- v = value(cell)
305
+ v = value(cell, inlined_blank)
271
306
  return :not_a_value if v == :not_a_value
272
307
  v
273
308
  end
@@ -2,10 +2,59 @@ class ReplaceArithmeticOnRangesAst
2
2
 
3
3
  def map(ast)
4
4
  return ast unless ast.is_a?(Array)
5
- arithmetic(ast) if ast.first == :arithmetic
6
5
  ast.each { |a| map(a) }
6
+ arithmetic(ast) if ast.first == :arithmetic
7
+ comparison(ast) if ast.first == :comparison
7
8
  ast
8
9
  end
10
+
11
+ # FIXME: DRY THIS UP
12
+ def comparison(ast)
13
+ left, operator, right = ast[1], ast[2], ast[3]
14
+ # Three different options, array on the left, array on the right, or both
15
+ # array on the left first
16
+ if left.first == :array && right.first != :array
17
+ map(right)
18
+ ast.replace(
19
+ array_map(left) do |cell|
20
+ [:comparison, map(cell), operator, right]
21
+ end
22
+ )
23
+
24
+ # array on the right next
25
+ elsif left.first != :array && right.first == :array
26
+ map(left)
27
+ ast.replace(
28
+ array_map(right) do |cell|
29
+ [:comparison, left, operator, map(cell)]
30
+ end
31
+ )
32
+
33
+ # now array both sides
34
+ elsif left.first == :array && right.first == :array
35
+ ast.replace(
36
+ left.map.with_index do |row, i|
37
+ if row == :array
38
+ row
39
+ else
40
+ row.map.with_index do |cell, j|
41
+ if cell == :row
42
+ cell
43
+ elsif i >= left.length || i >= right.length || j >= left[1].length || j >= right[1].length
44
+ [:error, "#VALUE!"]
45
+ else
46
+ [:comparison, map(left[i][j]), operator, map(right[i][j])]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ )
52
+ else
53
+ map(left)
54
+ map(right)
55
+ end
56
+ end
57
+
9
58
 
10
59
  # Format [:artithmetic, left, operator, right]
11
60
  # should have removed arithmetic with more than one operator
@@ -41,7 +90,7 @@ class ReplaceArithmeticOnRangesAst
41
90
  row.map.with_index do |cell, j|
42
91
  if cell == :row
43
92
  cell
44
- elsif i >= left.length || i >= right.length || j >= left.first.length || j >= right.first.length
93
+ elsif i >= left.length || i >= right.length || j >= left[1].length || j >= right[1].length
45
94
  [:error, "#VALUE!"]
46
95
  else
47
96
  [:arithmetic, map(left[i][j]), operator, map(right[i][j])]
@@ -12,7 +12,12 @@ class ReplaceArraysWithSingleCellsAst
12
12
  return unless ast.is_a?(Array)
13
13
  if ast.first == :array
14
14
  @need_to_replace = true
15
- return try_and_convert_array(ast)
15
+ new_ast = try_and_convert_array(ast)
16
+ return ERROR if new_ast.first == :array
17
+ return new_ast
18
+ # Special case, only change if at the top level
19
+ elsif ast[0] == :function && ast[1] == :CHOOSE && check_choose(ast)
20
+ # Replacement made in check
16
21
  else
17
22
  do_map(ast)
18
23
  ast
@@ -39,11 +44,62 @@ class ReplaceArraysWithSingleCellsAst
39
44
  else
40
45
  ast[1..-1].each { |a| do_map(a) }
41
46
  end
47
+ when :function
48
+ if ast[1] == :SUMIF && ast[3].first == :array
49
+ ast[3] = try_and_convert_array(ast[3])
50
+ elsif ast[1] == :SUMIFS && check_sumifs(ast)
51
+ # Replacement madein check_sumif function
52
+ elsif ast[1] == :MATCH && check_match(ast)
53
+ # Replacement made in check_match function
54
+ elsif ast[1] == :INDIRECT && check_indirect(ast)
55
+ # Replacement made in check function
56
+ else
57
+ ast[2..-1].each { |a| do_map(a) }
58
+ end
42
59
  else
43
60
  ast[1..-1].each { |a| do_map(a) }
44
61
  end
45
62
  end
46
63
 
64
+ def check_choose(ast)
65
+ replacement_made = false
66
+ i = 2
67
+ while i < ast.length
68
+ if ast[i].first == :array
69
+ replacement_made = true
70
+ ast[i] = try_and_convert_array(ast[i])
71
+ end
72
+ i +=1
73
+ end
74
+ replacement_made
75
+ end
76
+
77
+ def check_match(ast)
78
+ return false unless ast[2].first == :array
79
+ ast[2] = try_and_convert_array(ast[2])
80
+ ast[3..-1].each { |a| do_map(a) }
81
+ true
82
+ end
83
+
84
+ def check_indirect(ast)
85
+ return false unless ast[2].first == :array
86
+ ast[2] = try_and_convert_array(ast[2])
87
+ true
88
+ end
89
+
90
+ def check_sumifs(ast)
91
+ replacement_made = false
92
+ i = 4
93
+ while i < ast.length
94
+ if ast[i].first == :array
95
+ replacement_made = true
96
+ ast[i] = try_and_convert_array(ast[i])
97
+ end
98
+ i +=2
99
+ end
100
+ replacement_made
101
+ end
102
+
47
103
  def try_and_convert_array(ast)
48
104
  return ast unless ast.first == :array
49
105
  return ast unless all_references?(ast)
@@ -53,7 +109,7 @@ class ReplaceArraysWithSingleCellsAst
53
109
  elsif ast[1].length == 2
54
110
  single_column(ast)
55
111
  else
56
- ERROR
112
+ ast
57
113
  end
58
114
  end
59
115
 
@@ -78,7 +134,7 @@ class ReplaceArraysWithSingleCellsAst
78
134
  sheet == s && column == c
79
135
  end
80
136
 
81
- match || ERROR
137
+ match || ast
82
138
  end
83
139
 
84
140
  def single_column(ast)
@@ -94,6 +150,6 @@ class ReplaceArraysWithSingleCellsAst
94
150
  sheet == s && row == r
95
151
  end
96
152
 
97
- match || ERROR
153
+ match || ast
98
154
  end
99
155
  end