excel_to_code 0.1.23 → 0.2.0

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