excel_to_code 0.1.23 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/src/commands/excel_to_c.rb +39 -92
  3. data/src/commands/excel_to_ruby.rb +9 -35
  4. data/src/commands/excel_to_x.rb +515 -536
  5. data/src/compile/c/a.out +0 -0
  6. data/src/compile/c/compile_named_reference_setters.rb +4 -6
  7. data/src/compile/c/compile_to_c.rb +34 -21
  8. data/src/compile/c/compile_to_c_header.rb +7 -7
  9. data/src/compile/c/excel_to_c_runtime.c +8 -4
  10. data/src/compile/c/map_formulae_to_c.rb +85 -86
  11. data/src/compile/c/map_values_to_c.rb +7 -1
  12. data/src/compile/c/map_values_to_c_structs.rb +1 -1
  13. data/src/compile/ruby/compile_to_ruby.rb +14 -11
  14. data/src/compile/ruby/compile_to_ruby_unit_test.rb +17 -10
  15. data/src/compile/ruby/map_formulae_to_ruby.rb +56 -56
  16. data/src/compile/ruby/map_values_to_ruby.rb +14 -2
  17. data/src/excel/area.rb +6 -8
  18. data/src/excel/excel_functions/hlookup.rb +1 -1
  19. data/src/excel/excel_functions/vlookup.rb +1 -1
  20. data/src/excel/formula_peg.rb +1 -1
  21. data/src/excel/formula_peg.txt +1 -1
  22. data/src/excel/reference.rb +4 -3
  23. data/src/excel/table.rb +4 -4
  24. data/src/extract.rb +1 -0
  25. data/src/extract/check_for_unknown_functions.rb +2 -2
  26. data/src/extract/extract_array_formulae.rb +9 -9
  27. data/src/extract/extract_everything.rb +140 -0
  28. data/src/extract/extract_formulae.rb +30 -20
  29. data/src/extract/extract_named_references.rb +37 -22
  30. data/src/extract/extract_relationships.rb +16 -3
  31. data/src/extract/extract_shared_formulae.rb +8 -11
  32. data/src/extract/extract_shared_formulae_targets.rb +1 -6
  33. data/src/extract/extract_shared_strings.rb +21 -8
  34. data/src/extract/extract_simple_formulae.rb +11 -6
  35. data/src/extract/extract_table.rb +26 -13
  36. data/src/extract/extract_values.rb +35 -11
  37. data/src/extract/extract_worksheet_dimensions.rb +13 -3
  38. data/src/extract/extract_worksheet_names.rb +16 -3
  39. data/src/extract/extract_worksheet_table_relationships.rb +16 -4
  40. data/src/extract/simple_extract_from_xml.rb +9 -11
  41. data/src/rewrite.rb +3 -0
  42. data/src/rewrite/ast_copy_formula.rb +5 -1
  43. data/src/rewrite/ast_expand_array_formulae.rb +71 -59
  44. data/src/rewrite/caching_formula_parser.rb +110 -0
  45. data/src/rewrite/rewrite_array_formulae.rb +21 -14
  46. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +41 -13
  47. data/src/rewrite/rewrite_shared_formulae.rb +17 -18
  48. data/src/rewrite/rewrite_values_to_ast.rb +2 -0
  49. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +28 -25
  50. data/src/simplify.rb +1 -0
  51. data/src/simplify/count_formula_references.rb +22 -23
  52. data/src/simplify/emergency_array_formula_replace_indirect_bodge.rb +44 -0
  53. data/src/simplify/identify_dependencies.rb +7 -8
  54. data/src/simplify/identify_repeated_formula_elements.rb +5 -6
  55. data/src/simplify/inline_formulae.rb +48 -48
  56. data/src/simplify/map_formulae_to_values.rb +197 -79
  57. data/src/simplify/remove_cells.rb +13 -6
  58. data/src/simplify/replace_arithmetic_on_ranges.rb +42 -28
  59. data/src/simplify/replace_arrays_with_single_cells.rb +11 -5
  60. data/src/simplify/replace_column_with_column_number.rb +31 -23
  61. data/src/simplify/replace_common_elements_in_formulae.rb +16 -17
  62. data/src/simplify/replace_indirects_with_references.rb +26 -21
  63. data/src/simplify/replace_named_references.rb +26 -31
  64. data/src/simplify/replace_offsets_with_references.rb +33 -34
  65. data/src/simplify/replace_ranges_with_array_literals.rb +48 -20
  66. data/src/simplify/replace_shared_strings.rb +15 -13
  67. data/src/simplify/replace_string_join_on_ranges.rb +7 -9
  68. data/src/simplify/replace_table_references.rb +16 -11
  69. data/src/simplify/replace_values_with_constants.rb +6 -4
  70. data/src/simplify/simplify_arithmetic.rb +33 -19
  71. data/src/simplify/sort_into_calculation_order.rb +13 -13
  72. data/src/simplify/wrap_formulae_that_return_arrays_and_are_not_in_arrays.rb +21 -13
  73. metadata +19 -2
@@ -0,0 +1,140 @@
1
+ require 'ox'
2
+
3
+ class ExtractEverythingFromWorkbook < ::Ox::Sax
4
+
5
+ attr_accessor :table_rids
6
+ attr_accessor :worksheets_dimensions
7
+ attr_accessor :values
8
+ attr_accessor :formulae_simple
9
+ attr_accessor :formulae_array
10
+ attr_accessor :formulae_shared
11
+ attr_accessor :formulae_shared_targets
12
+
13
+ def initialize
14
+ # State
15
+ @current_element = []
16
+
17
+ # For parsing formulae
18
+ @fp = CachingFormulaParser.instance
19
+
20
+ # Outputs
21
+ @table_rids ||= {}
22
+ @worksheets_dimensions ||= {}
23
+ @values ||= {}
24
+ @formulae_simple ||= {}
25
+ @formulae_array ||= {}
26
+ @formulae_shared ||= {}
27
+ @formulae_shared_targets ||= {}
28
+ end
29
+
30
+ def extract(sheet_name, input_xml)
31
+ @sheet_name = sheet_name.to_sym
32
+ Ox.sax_parse(self, input_xml, :convert_special => true)
33
+ self
34
+ end
35
+
36
+ def start_element(name)
37
+ case name
38
+ when :v # :v is value
39
+ @current_element.push(name)
40
+ @value = []
41
+ when :f # :f is formula
42
+ @current_element.push(name)
43
+ @formula = []
44
+ @formula_type = 'simple' # Default is a simple formula, alternatives are shared or array
45
+ @formula_ref = nil # Used to specify range for shared or array formulae
46
+ @si = nil # Used to specify the index for shared formulae
47
+ when :c # :c is cell, wraps values and formulas
48
+ @current_element.push(name)
49
+ @ref = nil
50
+ @value_type = 'n' #Default type is number
51
+ when :dimension, :tablePart
52
+ @current_element.push(name)
53
+ end
54
+ end
55
+
56
+ def key
57
+ [@sheet_name, @ref]
58
+ end
59
+
60
+ def end_element(name)
61
+ case name
62
+ when :v
63
+ @current_element.pop
64
+ value = @value.join
65
+ ast = case @value_type
66
+ when 'b'; value == "1" ? [:boolean_true] : [:boolean_false]
67
+ when 's'; [:shared_string, value.to_i]
68
+ when 'n'; [:number, value.to_f]
69
+ when 'e'; [:error, value.to_sym]
70
+ when 'str'; [:string, value.gsub(/_x[0-9A-F]{4}_/,'').freeze]
71
+ else
72
+ $stderr.puts "Value of type #{@value_type} not known #{@sheet_name} #{@ref}"
73
+ exit
74
+ end
75
+ @values[key] = @fp.map(ast)
76
+ when :f
77
+ @current_element.pop
78
+ unless @formula.empty?
79
+ formula_text = @formula.join.gsub(/[\r\n]+/,'')
80
+ ast = @fp.parse(formula_text)
81
+ unless ast
82
+ $stderr.puts "Could not parse #{@sheet_name} #{@ref} #{formula_text}"
83
+ exit
84
+ end
85
+ end
86
+ case @formula_type
87
+ when 'simple'
88
+ return if @formula.empty?
89
+ @formulae_simple[key] = ast
90
+ when 'shared'
91
+ @formulae_shared_targets[key] = @si
92
+ if ast
93
+ @formulae_shared[key] = [@formula_ref, @si, ast]
94
+ end
95
+ when 'array'
96
+ @formulae_array[key] = [@formula_ref, ast]
97
+ end
98
+ when :dimension, :tablePart, :c
99
+ @current_element.pop
100
+ end
101
+ end
102
+
103
+ def attr(name, value)
104
+ case @current_element.last
105
+ when :dimension
106
+ return unless name == :ref
107
+ @worksheets_dimensions[@sheet_name] = value
108
+ when :tablePart
109
+ return unless name == :'r:id'
110
+ @table_rids[@sheet_name] ||= []
111
+ @table_rids[@sheet_name] << value
112
+ when :c
113
+ case name
114
+ when :r
115
+ @ref = value.to_sym
116
+ when :t
117
+ @value_type = value
118
+ end
119
+ when :f
120
+ case name
121
+ when :t
122
+ @formula_type = value
123
+ when :si
124
+ @si = value
125
+ when :ref
126
+ @formula_ref = value
127
+ end
128
+ end
129
+ end
130
+
131
+ def text(text)
132
+ case @current_element.last
133
+ when :v
134
+ @value << text
135
+ when :f
136
+ @formula << text
137
+ end
138
+ end
139
+
140
+ end
@@ -1,36 +1,46 @@
1
- require_relative 'simple_extract_from_xml'
1
+ require 'nokogiri'
2
+
3
+ class ExtractFormulae < Nokogiri::XML::SAX::Document
4
+
5
+ def self.extract(*args)
6
+ self.new.extract(*args)
7
+ end
8
+
9
+ def extract(sheet_name, input)
10
+ @sheet_name = sheet_name.to_sym
11
+ @output = {}
12
+ @parsing = false
13
+ Nokogiri::XML::SAX::Parser.new(self).parse(input)
14
+ @output
15
+ end
2
16
 
3
- class ExtractFormulae < SimpleExtractFromXML
4
-
5
- attr_accessor :ref, :formula
6
-
7
17
  def start_element(name,attributes)
8
18
  if name == 'c'
9
- @ref = attributes.assoc('r').last
19
+ @ref = attributes.assoc('r').last.to_sym
10
20
  elsif name == "f"
11
21
  type = attributes.assoc('t')
12
22
  @formula = []
13
23
  start_formula( type && type.last, attributes)
14
24
  end
15
25
  end
16
-
17
- def start_formula(type,attributes)
18
- # Should be overriden in sub classes
19
- end
20
-
21
- def write_formula
22
- # Should be overriden in sub classes
23
- end
24
-
26
+
25
27
  def end_element(name)
26
- return unless parsing && name == "f"
27
- self.parsing = false
28
+ return unless @parsing && name == "f"
29
+ @parsing = false
28
30
  write_formula
29
31
  end
30
-
32
+
31
33
  def characters(string)
32
- return unless parsing
34
+ return unless @parsing
33
35
  @formula.push(string)
34
36
  end
35
-
37
+
38
+ def start_formula(type,attributes)
39
+ # Override
40
+ end
41
+
42
+ def write_formula
43
+ # Override
44
+ end
45
+
36
46
  end
@@ -1,38 +1,53 @@
1
- require_relative 'simple_extract_from_xml'
1
+ require 'nokogiri'
2
+
3
+ class ExtractNamedReferences < Nokogiri::XML::SAX::Document
4
+
5
+ attr_accessor :parsing, :input, :output
6
+
7
+ def self.extract(input)
8
+ self.new.extract(input)
9
+ end
2
10
 
3
- class ExtractNamedReferences < SimpleExtractFromXML
4
-
5
- attr_accessor :sheet_names
6
-
7
11
  def initialize
8
12
  super
9
13
  @sheet_names = []
10
14
  end
11
-
15
+
16
+ def extract(input)
17
+ @input, @output = input, {}
18
+ @sheet = nil
19
+ @name = nil
20
+ @reference = nil
21
+ parser = Nokogiri::XML::SAX::Parser.new(self)
22
+ parser.parse(@input)
23
+ @output
24
+ end
25
+
12
26
  def start_element(name,attributes)
13
27
  if name == "sheet"
14
- sheet_names << attributes.assoc('name').last
28
+ @sheet_names << attributes.assoc('name').last
15
29
  elsif name == "definedName"
16
- @parsing = true
17
- sheet = attributes.assoc('localSheetId')
18
- if sheet
19
- output.write sheet_names[sheet.last.to_i]
20
- end
21
- output.write "\t"
22
- output.write attributes.assoc('name').last
23
- output.write "\t"
30
+ @sheet = attributes.assoc('localSheetId') && @sheet_names[attributes.assoc('localSheetId').last.to_i].downcase.to_sym
31
+ @name = attributes.assoc('name').last.downcase.to_sym
32
+ @reference = ""
24
33
  end
25
34
  end
26
-
35
+
27
36
  def end_element(name)
28
37
  return unless name == "definedName"
29
- self.parsing = false
30
- output.putc "\n"
38
+ if @sheet
39
+ @output[[@sheet, @name]] = @reference.gsub('$', '')
40
+ else
41
+ @output[@name] = @reference.gsub('$', '')
42
+ end
43
+ @sheet = nil
44
+ @name = nil
45
+ @reference = nil
31
46
  end
32
-
47
+
33
48
  def characters(string)
34
- return unless parsing
35
- output.write string
49
+ return unless @reference
50
+ @reference << string
36
51
  end
37
-
52
+
38
53
  end
@@ -1,10 +1,23 @@
1
- require_relative 'simple_extract_from_xml'
1
+ require 'nokogiri'
2
2
 
3
- class ExtractRelationships < SimpleExtractFromXML
3
+ class ExtractRelationships < Nokogiri::XML::SAX::Document
4
+
5
+ attr_accessor :input, :output
6
+
7
+ def self.extract(*args)
8
+ self.new.extract(*args)
9
+ end
10
+
11
+ def extract(input)
12
+ @input, @output = input, {}
13
+ parser = Nokogiri::XML::SAX::Parser.new(self)
14
+ parser.parse(input)
15
+ output
16
+ end
4
17
 
5
18
  def start_element(name,attributes)
6
19
  return false unless name == "Relationship"
7
- output.puts "#{attributes.assoc('Id').last}\t#{attributes.assoc('Target').last}"
20
+ @output[attributes.assoc('Id').last] = attributes.assoc('Target').last
8
21
  end
9
22
 
10
23
  end
@@ -2,9 +2,6 @@ require_relative 'extract_formulae'
2
2
 
3
3
  class ExtractSharedFormulae < ExtractFormulae
4
4
 
5
- attr_accessor :shared_range
6
- attr_accessor :shared_formula_identifier
7
-
8
5
  def start_formula(type,attributes)
9
6
  return unless type == 'shared' && attributes.assoc('ref')
10
7
  @shared_range = attributes.assoc('ref').last
@@ -14,14 +11,14 @@ class ExtractSharedFormulae < ExtractFormulae
14
11
 
15
12
  def write_formula
16
13
  return if @formula.empty?
17
- output.write @ref
18
- output.write "\t"
19
- output.write @shared_range
20
- output.write "\t"
21
- output.write @shared_formula_identifier
22
- output.write "\t"
23
- output.write @formula.join.gsub(/[\n\r]+/,'')
24
- output.write "\n"
14
+ formula_text = @formula.join.gsub(/[\r\n]+/,'')
15
+ ast = CachingFormulaParser.parse(formula_text)
16
+ unless ast
17
+ $stderr.puts "Could not parse #{@sheet_name} #{@ref} #{formula_text}"
18
+ exit
19
+ end
20
+ # FIXME: Should leave in original form rather than converting to ast?
21
+ @output[[@sheet_name, @ref]] = [@shared_range, @shared_formula_identifier, ast]
25
22
  end
26
23
 
27
24
  end
@@ -2,8 +2,6 @@ require_relative 'extract_formulae'
2
2
 
3
3
  class ExtractSharedFormulaeTargets < ExtractFormulae
4
4
 
5
- attr_accessor :shared_formula_identifier
6
-
7
5
  def start_formula(type,attributes)
8
6
  return unless type == 'shared'
9
7
  @shared_formula_identifier = attributes.assoc('si').last
@@ -11,10 +9,7 @@ class ExtractSharedFormulaeTargets < ExtractFormulae
11
9
  end
12
10
 
13
11
  def write_formula
14
- output.write @ref
15
- output.write "\t"
16
- output.write @shared_formula_identifier
17
- output.write "\n"
12
+ @output[[@sheet_name, @ref]] = @shared_formula_identifier
18
13
  end
19
14
 
20
15
  end
@@ -1,20 +1,33 @@
1
- require_relative 'simple_extract_from_xml'
1
+ require 'nokogiri'
2
2
 
3
- class ExtractSharedStrings < SimpleExtractFromXML
3
+ class ExtractSharedStrings < Nokogiri::XML::SAX::Document
4
+
5
+ def self.extract(input)
6
+ self.new.extract(input)
7
+ end
8
+
9
+ def extract(input)
10
+ @input, @output = input, []
11
+ @current = nil
12
+ parser = Nokogiri::XML::SAX::Parser.new(self)
13
+ parser.parse(input)
14
+ @output
15
+ end
4
16
 
5
17
  def start_element(name,attributes)
6
- self.parsing = true if name == "si"
18
+ @current = [] if name == "si"
7
19
  end
8
20
 
9
21
  def end_element(name)
10
22
  return unless name == "si"
11
- self.parsing = false
12
- output.putc "\n"
23
+ @output << [:string, @current.join]
24
+ @current = nil
13
25
  end
14
26
 
15
27
  def characters(string)
16
- return unless parsing
17
- output.write string.gsub("\n","")
28
+ return unless @current
29
+ # FIXME: SHOULDN'T ELINMATE NEWLINES
30
+ @current << string.gsub("\n","")
18
31
  end
19
32
 
20
- end
33
+ end
@@ -1,18 +1,23 @@
1
1
  require_relative 'extract_formulae'
2
2
 
3
3
  class ExtractSimpleFormulae < ExtractFormulae
4
-
4
+
5
5
  def start_formula(type,attributes)
6
+ # Simple formulas don't have a type
6
7
  return if type
7
8
  @parsing = true
8
9
  end
9
-
10
+
10
11
  def write_formula
11
12
  return if @formula.empty?
12
- output.write @ref
13
- output.write "\t"
14
- output.write @formula.join.gsub(/[\r\n]+/,'')
15
- output.write "\n"
13
+ formula_text = @formula.join.gsub(/[\r\n]+/,'')
14
+ ast = CachingFormulaParser.parse(formula_text)
15
+ unless ast
16
+ $stderr.puts "Could not parse #{@sheet_name} #{@ref} #{formula_text}"
17
+ exit
18
+ end
19
+ # FIXME: Should leave in original form rather than converting to ast?
20
+ @output[[@sheet_name, @ref]] = ast
16
21
  end
17
22
 
18
23
  end
@@ -1,24 +1,37 @@
1
- require_relative 'simple_extract_from_xml'
1
+ require 'nokogiri'
2
2
 
3
- class ExtractTable < SimpleExtractFromXML
4
-
5
- attr_accessor :worksheet_name
6
-
7
- def initialize(worksheet_name = nil)
8
- super()
9
- @worksheet_name = worksheet_name
10
- end
11
-
3
+ class ExtractTable < Nokogiri::XML::SAX::Document
4
+
5
+ def self.extract(*args)
6
+ self.new.extract(*args)
7
+ end
8
+
9
+ def extract(sheet_name, input)
10
+ @sheet_name = sheet_name
11
+ @output = {}
12
+ @table_name = nil
13
+ @table_ref = nil
14
+ @table_total_rows = nil
15
+ @table_columns = nil
16
+
17
+ @parsing = false
18
+ Nokogiri::XML::SAX::Parser.new(self).parse(input)
19
+ @output
20
+ end
21
+
12
22
  def start_element(name,attributes)
13
23
  if name == "table"
14
- output.write "#{attributes.assoc('displayName').last}\t#{@worksheet_name}\t#{attributes.assoc('ref').last}\t#{attributes.assoc('totalsRowCount').try(:last) || 0}"
24
+ @table_name = attributes.assoc('displayName').last
25
+ @table_ref = attributes.assoc('ref').last
26
+ @table_total_rows = attributes.assoc('totalsRowCount').try(:last) || "0"
27
+ @table_columns = []
15
28
  elsif name == "tableColumn"
16
- output.write "\t#{attributes.assoc('name').last}"
29
+ @table_columns << attributes.assoc('name').last
17
30
  end
18
31
  end
19
32
 
20
33
  def end_element(name)
21
34
  return unless name == "table"
22
- output.putc "\n"
35
+ @output[@table_name] = [@sheet_name, @table_ref, @table_total_rows, *@table_columns]
23
36
  end
24
37
  end