excel_to_code 0.2.15 → 0.2.16

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