excel_to_code 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/README +41 -0
  2. data/bin/excel_to_c +63 -0
  3. data/bin/excel_to_ruby +9 -0
  4. data/src/commands.rb +2 -0
  5. data/src/commands/excel_to_c.rb +858 -0
  6. data/src/commands/excel_to_ruby.rb +620 -0
  7. data/src/compile.rb +2 -0
  8. data/src/compile/c.rb +5 -0
  9. data/src/compile/c/compile_to_c.rb +62 -0
  10. data/src/compile/c/compile_to_c_header.rb +26 -0
  11. data/src/compile/c/compile_to_c_unit_test.rb +42 -0
  12. data/src/compile/c/excel_to_c_runtime.c +2029 -0
  13. data/src/compile/c/map_formulae_to_c.rb +184 -0
  14. data/src/compile/c/map_sheet_names_to_c_names.rb +19 -0
  15. data/src/compile/c/map_values_to_c.rb +85 -0
  16. data/src/compile/c/map_values_to_c_structs.rb +37 -0
  17. data/src/compile/ruby.rb +3 -0
  18. data/src/compile/ruby/compile_to_ruby.rb +33 -0
  19. data/src/compile/ruby/compile_to_ruby_unit_test.rb +28 -0
  20. data/src/compile/ruby/excel_to_ruby_runtime.rb +1 -0
  21. data/src/compile/ruby/map_formulae_to_ruby.rb +95 -0
  22. data/src/compile/ruby/map_sheet_names_to_ruby_names.rb +19 -0
  23. data/src/compile/ruby/map_values_to_ruby.rb +65 -0
  24. data/src/excel.rb +5 -0
  25. data/src/excel/area.rb +93 -0
  26. data/src/excel/excel_functions.rb +84 -0
  27. data/src/excel/excel_functions/abs.rb +14 -0
  28. data/src/excel/excel_functions/add.rb +18 -0
  29. data/src/excel/excel_functions/and.rb +30 -0
  30. data/src/excel/excel_functions/apply_to_range.rb +17 -0
  31. data/src/excel/excel_functions/average.rb +12 -0
  32. data/src/excel/excel_functions/choose.rb +18 -0
  33. data/src/excel/excel_functions/cosh.rb +9 -0
  34. data/src/excel/excel_functions/count.rb +9 -0
  35. data/src/excel/excel_functions/counta.rb +8 -0
  36. data/src/excel/excel_functions/divide.rb +23 -0
  37. data/src/excel/excel_functions/excel_equal.rb +20 -0
  38. data/src/excel/excel_functions/excel_if.rb +8 -0
  39. data/src/excel/excel_functions/excel_match.rb +51 -0
  40. data/src/excel/excel_functions/find.rb +39 -0
  41. data/src/excel/excel_functions/iferror.rb +10 -0
  42. data/src/excel/excel_functions/index.rb +48 -0
  43. data/src/excel/excel_functions/left.rb +12 -0
  44. data/src/excel/excel_functions/less_than.rb +26 -0
  45. data/src/excel/excel_functions/less_than_or_equal.rb +26 -0
  46. data/src/excel/excel_functions/max.rb +12 -0
  47. data/src/excel/excel_functions/min.rb +12 -0
  48. data/src/excel/excel_functions/mod.rb +15 -0
  49. data/src/excel/excel_functions/more_than.rb +26 -0
  50. data/src/excel/excel_functions/more_than_or_equal.rb +26 -0
  51. data/src/excel/excel_functions/multiply.rb +24 -0
  52. data/src/excel/excel_functions/negative.rb +12 -0
  53. data/src/excel/excel_functions/not_equal.rb +19 -0
  54. data/src/excel/excel_functions/number_argument.rb +30 -0
  55. data/src/excel/excel_functions/pi.rb +7 -0
  56. data/src/excel/excel_functions/pmt.rb +16 -0
  57. data/src/excel/excel_functions/power.rb +18 -0
  58. data/src/excel/excel_functions/round.rb +13 -0
  59. data/src/excel/excel_functions/rounddown.rb +14 -0
  60. data/src/excel/excel_functions/roundup.rb +17 -0
  61. data/src/excel/excel_functions/string_join.rb +19 -0
  62. data/src/excel/excel_functions/subtotal.rb +13 -0
  63. data/src/excel/excel_functions/subtract.rb +18 -0
  64. data/src/excel/excel_functions/sum.rb +8 -0
  65. data/src/excel/excel_functions/sumif.rb +7 -0
  66. data/src/excel/excel_functions/sumifs.rb +74 -0
  67. data/src/excel/excel_functions/sumproduct.rb +32 -0
  68. data/src/excel/excel_functions/vlookup.rb +49 -0
  69. data/src/excel/formula_peg.rb +238 -0
  70. data/src/excel/formula_peg.txt +45 -0
  71. data/src/excel/reference.rb +56 -0
  72. data/src/excel/table.rb +108 -0
  73. data/src/excel_to_code.rb +7 -0
  74. data/src/extract.rb +13 -0
  75. data/src/extract/check_for_unknown_functions.rb +20 -0
  76. data/src/extract/extract_array_formulae.rb +23 -0
  77. data/src/extract/extract_formulae.rb +36 -0
  78. data/src/extract/extract_named_references.rb +38 -0
  79. data/src/extract/extract_relationships.rb +10 -0
  80. data/src/extract/extract_shared_formulae.rb +23 -0
  81. data/src/extract/extract_shared_strings.rb +20 -0
  82. data/src/extract/extract_simple_formulae.rb +18 -0
  83. data/src/extract/extract_table.rb +24 -0
  84. data/src/extract/extract_values.rb +29 -0
  85. data/src/extract/extract_worksheet_dimensions.rb +11 -0
  86. data/src/extract/extract_worksheet_names.rb +10 -0
  87. data/src/extract/extract_worksheet_table_relationships.rb +10 -0
  88. data/src/extract/simple_extract_from_xml.rb +19 -0
  89. data/src/rewrite.rb +10 -0
  90. data/src/rewrite/ast_copy_formula.rb +42 -0
  91. data/src/rewrite/ast_expand_array_formulae.rb +180 -0
  92. data/src/rewrite/rewrite_array_formulae.rb +71 -0
  93. data/src/rewrite/rewrite_array_formulae_to_arrays.rb +18 -0
  94. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +56 -0
  95. data/src/rewrite/rewrite_formulae_to_ast.rb +24 -0
  96. data/src/rewrite/rewrite_merge_formulae_and_values.rb +18 -0
  97. data/src/rewrite/rewrite_relationship_id_to_filename.rb +22 -0
  98. data/src/rewrite/rewrite_shared_formulae.rb +38 -0
  99. data/src/rewrite/rewrite_values_to_ast.rb +28 -0
  100. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +90 -0
  101. data/src/rewrite/rewrite_worksheet_names.rb +20 -0
  102. data/src/simplify.rb +16 -0
  103. data/src/simplify/count_formula_references.rb +58 -0
  104. data/src/simplify/identify_dependencies.rb +56 -0
  105. data/src/simplify/identify_repeated_formula_elements.rb +37 -0
  106. data/src/simplify/inline_formulae.rb +77 -0
  107. data/src/simplify/map_formulae_to_values.rb +157 -0
  108. data/src/simplify/remove_cells.rb +18 -0
  109. data/src/simplify/replace_arrays_with_single_cells.rb +27 -0
  110. data/src/simplify/replace_blanks.rb +58 -0
  111. data/src/simplify/replace_common_elements_in_formulae.rb +19 -0
  112. data/src/simplify/replace_formulae_with_calculated_values.rb +21 -0
  113. data/src/simplify/replace_indirects_with_references.rb +44 -0
  114. data/src/simplify/replace_named_references.rb +82 -0
  115. data/src/simplify/replace_ranges_with_array_literals.rb +54 -0
  116. data/src/simplify/replace_shared_strings.rb +49 -0
  117. data/src/simplify/replace_table_references.rb +71 -0
  118. data/src/simplify/replace_values_with_constants.rb +47 -0
  119. data/src/simplify/simplify_arithmetic.rb +54 -0
  120. data/src/util.rb +2 -0
  121. data/src/util/not_supported_exception.rb +2 -0
  122. data/src/util/try.rb +9 -0
  123. metadata +207 -0
@@ -0,0 +1,29 @@
1
+ require_relative 'simple_extract_from_xml'
2
+
3
+ class ExtractValues < SimpleExtractFromXML
4
+
5
+ attr_accessor :ref, :type
6
+
7
+ def start_element(name,attributes)
8
+ if name == "v"
9
+ self.parsing = true
10
+ output.write "#{@ref}\t#{@type}\t"
11
+ elsif name == "c"
12
+ @ref = attributes.assoc('r').last
13
+ type = attributes.assoc('t')
14
+ @type = type ? type.last : "n"
15
+ end
16
+ end
17
+
18
+ def end_element(name)
19
+ return unless name == "v"
20
+ self.parsing = false
21
+ output.putc "\n"
22
+ end
23
+
24
+ def characters(string)
25
+ return unless parsing
26
+ output.write string
27
+ end
28
+
29
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'simple_extract_from_xml'
2
+
3
+ class ExtractWorksheetDimensions < SimpleExtractFromXML
4
+
5
+ # FIXME: Is there an elegant way to abort once we have found the dimension tag?
6
+ def start_element(name,attributes)
7
+ return false unless name == "dimension"
8
+ output.puts "#{attributes.assoc('ref').last}"
9
+ end
10
+
11
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'simple_extract_from_xml'
2
+
3
+ class ExtractWorksheetNames < SimpleExtractFromXML
4
+
5
+ def start_element(name,attributes)
6
+ return false unless name == "sheet"
7
+ output.puts "#{attributes.assoc('r:id').last}\t#{attributes.assoc('name').last}"
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'simple_extract_from_xml'
2
+
3
+ class ExtractWorksheetTableRelationships < SimpleExtractFromXML
4
+
5
+ def start_element(name,attributes)
6
+ return false unless name == "tablePart"
7
+ output.puts "#{attributes.assoc('r:id').last}"
8
+ end
9
+
10
+ end
@@ -0,0 +1,19 @@
1
+ require 'nokogiri'
2
+
3
+ class SimpleExtractFromXML < Nokogiri::XML::SAX::Document
4
+
5
+ attr_accessor :parsing, :input, :output
6
+
7
+ def self.extract(input,output)
8
+ self.new.extract(input,output)
9
+ end
10
+
11
+ def extract(input,output)
12
+ @input, @output = input, output
13
+ parsing = false
14
+ parser = Nokogiri::XML::SAX::Parser.new(self)
15
+ parser.parse(input)
16
+ output
17
+ end
18
+
19
+ end
data/src/rewrite.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative "rewrite/rewrite_formulae_to_ast"
2
+ require_relative "rewrite/rewrite_worksheet_names"
3
+ require_relative "rewrite/rewrite_whole_row_column_references_to_areas"
4
+ require_relative "rewrite/rewrite_shared_formulae"
5
+ require_relative "rewrite/rewrite_array_formulae_to_arrays"
6
+ require_relative "rewrite/rewrite_array_formulae"
7
+ require_relative "rewrite/rewrite_values_to_ast"
8
+ require_relative "rewrite/rewrite_relationship_id_to_filename"
9
+ require_relative "rewrite/rewrite_merge_formulae_and_values"
10
+ require_relative "rewrite/rewrite_cell_references_to_include_sheet"
@@ -0,0 +1,42 @@
1
+ require_relative '../excel'
2
+ require_relative '../util/not_supported_exception'
3
+
4
+ class AstCopyFormula
5
+ attr_accessor :rows_to_move
6
+ attr_accessor :columns_to_move
7
+
8
+ def initialize
9
+ self.rows_to_move = 0
10
+ self.columns_to_move = 0
11
+ end
12
+
13
+ def copy(ast)
14
+ return ast unless ast.is_a?(Array)
15
+ operator = ast[0]
16
+ if respond_to?(operator)
17
+ send(operator,*ast[1..-1])
18
+ else
19
+ [operator,*ast[1..-1].map {|a| copy(a) }]
20
+ end
21
+ end
22
+
23
+ def cell(reference)
24
+ r = Reference.for(reference)
25
+ [:cell,r.offset(rows_to_move,columns_to_move)]
26
+ end
27
+
28
+ def area(start,finish)
29
+ s = Reference.for(start).offset(rows_to_move,columns_to_move)
30
+ f = Reference.for(finish).offset(rows_to_move,columns_to_move)
31
+ [:area,s,f]
32
+ end
33
+
34
+ def column_range(reference)
35
+ raise NotSupportedException.new("Column ranges not suported in AstCopyFormula")
36
+ end
37
+
38
+ def row_range(reference)
39
+ raise NotSupportedException.new("Row ranges not suported in AstCopyFormula")
40
+ end
41
+
42
+ end
@@ -0,0 +1,180 @@
1
+ require_relative '../excel'
2
+
3
+ class AstExpandArrayFormulae
4
+
5
+ def map(ast)
6
+ return ast unless ast.is_a?(Array)
7
+ operator = ast[0]
8
+ if respond_to?(operator)
9
+ send(operator,*ast[1..-1])
10
+ else
11
+ [operator,*ast[1..-1].map {|a| map(a) }]
12
+ end
13
+ end
14
+
15
+ def arithmetic(left,operator,right)
16
+ left = map(left)
17
+ right = map(right)
18
+ return [:arithmetic, left, operator, right] unless array?(left,right)
19
+
20
+ map_arrays([left,right]) do |arrayed|
21
+ [:arithmetic,arrayed[0],operator,arrayed[1]]
22
+ end
23
+ end
24
+
25
+ def comparison(left,operator,right)
26
+ left = map(left)
27
+ right = map(right)
28
+ return [:comparison, left, operator, right] unless array?(left,right)
29
+
30
+ map_arrays([left,right]) do |arrayed|
31
+ [:comparison,arrayed[0],operator,arrayed[1]]
32
+ end
33
+ end
34
+
35
+ def string_join(*strings)
36
+ strings = strings.map { |s| map(s) }
37
+ return [:string_join, *strings] unless array?(*strings)
38
+ map_arrays(strings) do |arrayed_strings|
39
+ [:string_join, *arrayed_strings]
40
+ end
41
+ end
42
+
43
+ def map_arrays(arrays, &block)
44
+ # Turn them into ruby arrays
45
+ arrays = arrays.map { |a| array_ast_to_ruby_array(a) }
46
+
47
+ # Find the largest one
48
+ max_rows = arrays.max { |a,b| a.length <=> b.length }.length
49
+ max_columns = arrays.max { |a,b| a.first.length <=> b.first.length }.first.length
50
+
51
+ # Convert any single rows into an array of the right size
52
+ arrays = arrays.map { |a| a.length == 1 ? Array.new(max_rows,a.first) : a }
53
+
54
+ # Convert any single columns into an array of the right size
55
+ arrays = arrays.map { |a| a.first.length == 1 ? Array.new(max_columns,a.flatten(1)).transpose : a }
56
+
57
+ # Now iterate through
58
+ return [:array, *max_rows.times.map do |row|
59
+ [:row, *max_columns.times.map do |column|
60
+ block.call(arrays.map do |a|
61
+ a[row][column] || [:error, "#N/A"]
62
+ end)
63
+ end]
64
+ end]
65
+ end
66
+
67
+ FUNCTIONS_THAT_ACCEPT_RANGES_FOR_ALL_ARGUMENTS = %w{AVERAGE COUNT COUNTA MAX MIN SUM SUMPRODUCT}
68
+
69
+ def function(name,*arguments)
70
+ if FUNCTIONS_THAT_ACCEPT_RANGES_FOR_ALL_ARGUMENTS.include?(name)
71
+ [:function, name, *arguments.map { |a| map(a) }]
72
+ elsif respond_to?("map_#{name.downcase}")
73
+ send("map_#{name.downcase}",*arguments)
74
+ else
75
+ function_that_does_not_accept_ranges(name,arguments)
76
+ end
77
+ end
78
+
79
+ def function_that_does_not_accept_ranges(name,arguments)
80
+ return [:function, name] if arguments.empty?
81
+ array_map arguments, name, *Array.new(arguments.length,false)
82
+ end
83
+
84
+ def map_match(*args)
85
+ $DEBUGN = true
86
+ a = array_map args, 'MATCH', false, true, false
87
+ $DEBUGN = false
88
+ a
89
+ end
90
+
91
+ def map_subtotal(*args)
92
+ array_map args, 'SUBTOTAL', false, *Array.new(args.length-1,true)
93
+ end
94
+
95
+ def map_index(*args)
96
+ array_map args, 'INDEX', true, false, false
97
+ end
98
+
99
+ def map_sumif(*args)
100
+ array_map args, 'SUMIF', true, false, true
101
+ end
102
+
103
+ def map_sumifs(*args)
104
+ if args.length > 3
105
+ array_map args, 'SUMIFS', true, true, false, *([true,false]*((args.length-3)/2))
106
+ else
107
+ array_map args, 'SUMIFS', true, true, false
108
+ end
109
+ end
110
+
111
+ def map_vlookup(*args)
112
+ array_map args, "VLOOKUP", false, true, false, false
113
+ end
114
+
115
+ private
116
+
117
+ def no_need_to_array?(args,ok_to_be_an_array)
118
+ ok_to_be_an_array.each_with_index do |array_ok,i|
119
+ unless array_ok
120
+ if args[i].first == :array
121
+ return false
122
+ end
123
+ end
124
+ end
125
+ true
126
+ end
127
+
128
+ def array_map(args,function,*ok_to_be_an_array)
129
+ args = args.map { |a| map(a) }
130
+ return [:function, function, *args ] if no_need_to_array?(args,ok_to_be_an_array)
131
+
132
+ # Turn the relevant arguments into ruby arrays and store the dimensions
133
+ # Enumerable#max and Enumerable#min don't return Enumerators, so can't do it using those methods
134
+ max_rows = 1
135
+ max_columns = 1
136
+ args = args.map.with_index do |a,i|
137
+ unless ok_to_be_an_array[i]
138
+ a = array_ast_to_ruby_array(a)
139
+ r = a.length
140
+ c = a.first.length
141
+ max_rows = r if r > max_rows
142
+ max_columns = c if c > max_columns
143
+ end
144
+ a
145
+ end
146
+
147
+ # Convert any single rows into an array of the right size
148
+ args = args.map.with_index { |a,i| (!ok_to_be_an_array[i] && a.length == 1) ? Array.new(max_rows,a.first) : a }
149
+
150
+ # Convert any single columns into an array of the right size
151
+ args = args.map.with_index { |a,i| (!ok_to_be_an_array[i] && a.first.length == 1) ? Array.new(max_columns,a.flatten(1)).transpose : a }
152
+
153
+ # Now iterate through
154
+ return [:array, *max_rows.times.map do |row|
155
+ [:row, *max_columns.times.map do |column|
156
+ [:function, function, *args.map.with_index do |a,i|
157
+ if ok_to_be_an_array[i]
158
+ a
159
+ else
160
+ a[row][column] || [:error, "#N/A"]
161
+ end
162
+ end]
163
+ end]
164
+ end]
165
+ end
166
+
167
+ def array?(*args)
168
+ args.any? { |a| a.first == :array }
169
+ end
170
+
171
+ def array_ast_to_ruby_array(array_ast)
172
+ return [[array_ast]] unless array_ast.first == :array
173
+ array_ast[1..-1].map do |row_ast|
174
+ row_ast[1..-1].map do |cell|
175
+ cell
176
+ end
177
+ end
178
+ end
179
+
180
+ end
@@ -0,0 +1,71 @@
1
+ class ExtractArrayFormulaForCell
2
+
3
+ attr_accessor :row_offset, :column_offset
4
+
5
+ def map(ast)
6
+ case ast.first
7
+ when :array; map_array(ast)
8
+ when :function; map_function(ast)
9
+ else return ast
10
+ end
11
+ end
12
+
13
+ def map_array(ast)
14
+ if (@row_offset + 1) >= ast.length
15
+ if ast.length == 2
16
+ @row_offset = 0
17
+ else
18
+ return [:error, "#N/A"]
19
+ end
20
+ end
21
+
22
+ if (@column_offset + 1) >= ast[1].length
23
+ if ast[1].length == 2
24
+ @column_offset = 0
25
+ else
26
+ return [:error, "#N/A"]
27
+ end
28
+ end
29
+
30
+ ast[@row_offset+1][@column_offset+1] # plus ones to skip tthe [:array,[:row,"cell"]] symbols
31
+ end
32
+
33
+ FUNCTIONS_THAT_CAN_RETURN_ARRAYS = %w{INDEX}
34
+
35
+ def map_function(ast)
36
+ return ast unless FUNCTIONS_THAT_CAN_RETURN_ARRAYS.include?(ast[1])
37
+ [:function, "INDEX", ast, [:number, (@row_offset+1).to_s], [:number, (column_offset+1).to_s]]
38
+ end
39
+
40
+ end
41
+
42
+ class RewriteArrayFormulae
43
+ def self.rewrite(input,output)
44
+ new.rewrite(input,output)
45
+ end
46
+
47
+ def rewrite(input,output)
48
+ input.lines do |line|
49
+ ref, array_range, formula = line.split("\t")
50
+ array_formula(formula,array_range,output)
51
+ end
52
+ end
53
+
54
+ def array_formula(formula,array_range,output)
55
+ array_ast = eval(formula)
56
+ array_range = "#{array_range}:#{array_range}" unless array_range.include?(':')
57
+ array_range = Area.for(array_range)
58
+ array_range.calculate_excel_variables
59
+ start_reference = array_range.excel_start
60
+ mapper = ExtractArrayFormulaForCell.new
61
+
62
+ # Then we rewrite each of the subsidiaries
63
+ array_range.offsets.each do |row,column|
64
+ mapper.row_offset = row
65
+ mapper.column_offset = column
66
+ ref = start_reference.offset(row,column)
67
+ output.puts "#{ref}\t#{mapper.map(array_ast).inspect}"
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "ast_expand_array_formulae"
2
+
3
+ class RewriteArrayFormulaeToArrays
4
+
5
+ def self.rewrite(*args)
6
+ new.rewrite(*args)
7
+ end
8
+
9
+ def rewrite(input,output)
10
+ mapper = AstExpandArrayFormulae.new
11
+ input.lines do |line|
12
+ content = line.split("\t")
13
+ ast = eval(content.pop)
14
+ output.puts "#{content.join("\t")}\t#{mapper.map(ast).inspect}"
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,56 @@
1
+ require_relative '../excel'
2
+
3
+ class RewriteCellReferencesToIncludeSheetAst
4
+
5
+ attr_accessor :worksheet
6
+
7
+ def map(ast)
8
+ if ast.is_a?(Array)
9
+ operator = ast.shift
10
+ if respond_to?(operator)
11
+ send(operator,*ast)
12
+ else
13
+ [operator,*ast.map {|a| map(a) }]
14
+ end
15
+ else
16
+ return ast
17
+ end
18
+ end
19
+
20
+ def cell(ref)
21
+ [:sheet_reference, worksheet, [:cell, ref]]
22
+ end
23
+
24
+ def area(start,finish)
25
+ [:sheet_reference, worksheet, [:area, start, finish]]
26
+ end
27
+
28
+ def sheet_reference(sheet_name,reference)
29
+ [:sheet_reference, sheet_name, reference]
30
+ end
31
+
32
+ end
33
+
34
+ class RewriteCellReferencesToIncludeSheet
35
+
36
+ def self.rewrite(*args)
37
+ new.rewrite(*args)
38
+ end
39
+
40
+ attr_accessor :worksheet
41
+
42
+ def rewrite(input,output)
43
+ mapper = RewriteCellReferencesToIncludeSheetAst.new
44
+ mapper.worksheet = worksheet
45
+ input.lines do |line|
46
+ if line =~ /(:area|:cell)/
47
+ content = line.split("\t")
48
+ ast = eval(content.pop)
49
+ output.puts "#{content.join("\t")}\t#{mapper.map(ast).inspect}"
50
+ else
51
+ output.puts line
52
+ end
53
+ end
54
+ end
55
+
56
+ end