excel_to_code 0.0.1

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