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.
- checksums.yaml +4 -4
- data/src/commands/excel_to_c.rb +39 -92
- data/src/commands/excel_to_ruby.rb +9 -35
- data/src/commands/excel_to_x.rb +515 -536
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/compile_named_reference_setters.rb +4 -6
- data/src/compile/c/compile_to_c.rb +34 -21
- data/src/compile/c/compile_to_c_header.rb +7 -7
- data/src/compile/c/excel_to_c_runtime.c +8 -4
- data/src/compile/c/map_formulae_to_c.rb +85 -86
- data/src/compile/c/map_values_to_c.rb +7 -1
- data/src/compile/c/map_values_to_c_structs.rb +1 -1
- data/src/compile/ruby/compile_to_ruby.rb +14 -11
- data/src/compile/ruby/compile_to_ruby_unit_test.rb +17 -10
- data/src/compile/ruby/map_formulae_to_ruby.rb +56 -56
- data/src/compile/ruby/map_values_to_ruby.rb +14 -2
- data/src/excel/area.rb +6 -8
- data/src/excel/excel_functions/hlookup.rb +1 -1
- data/src/excel/excel_functions/vlookup.rb +1 -1
- data/src/excel/formula_peg.rb +1 -1
- data/src/excel/formula_peg.txt +1 -1
- data/src/excel/reference.rb +4 -3
- data/src/excel/table.rb +4 -4
- data/src/extract.rb +1 -0
- data/src/extract/check_for_unknown_functions.rb +2 -2
- data/src/extract/extract_array_formulae.rb +9 -9
- data/src/extract/extract_everything.rb +140 -0
- data/src/extract/extract_formulae.rb +30 -20
- data/src/extract/extract_named_references.rb +37 -22
- data/src/extract/extract_relationships.rb +16 -3
- data/src/extract/extract_shared_formulae.rb +8 -11
- data/src/extract/extract_shared_formulae_targets.rb +1 -6
- data/src/extract/extract_shared_strings.rb +21 -8
- data/src/extract/extract_simple_formulae.rb +11 -6
- data/src/extract/extract_table.rb +26 -13
- data/src/extract/extract_values.rb +35 -11
- data/src/extract/extract_worksheet_dimensions.rb +13 -3
- data/src/extract/extract_worksheet_names.rb +16 -3
- data/src/extract/extract_worksheet_table_relationships.rb +16 -4
- data/src/extract/simple_extract_from_xml.rb +9 -11
- data/src/rewrite.rb +3 -0
- data/src/rewrite/ast_copy_formula.rb +5 -1
- data/src/rewrite/ast_expand_array_formulae.rb +71 -59
- data/src/rewrite/caching_formula_parser.rb +110 -0
- data/src/rewrite/rewrite_array_formulae.rb +21 -14
- data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +41 -13
- data/src/rewrite/rewrite_shared_formulae.rb +17 -18
- data/src/rewrite/rewrite_values_to_ast.rb +2 -0
- data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +28 -25
- data/src/simplify.rb +1 -0
- data/src/simplify/count_formula_references.rb +22 -23
- data/src/simplify/emergency_array_formula_replace_indirect_bodge.rb +44 -0
- data/src/simplify/identify_dependencies.rb +7 -8
- data/src/simplify/identify_repeated_formula_elements.rb +5 -6
- data/src/simplify/inline_formulae.rb +48 -48
- data/src/simplify/map_formulae_to_values.rb +197 -79
- data/src/simplify/remove_cells.rb +13 -6
- data/src/simplify/replace_arithmetic_on_ranges.rb +42 -28
- data/src/simplify/replace_arrays_with_single_cells.rb +11 -5
- data/src/simplify/replace_column_with_column_number.rb +31 -23
- data/src/simplify/replace_common_elements_in_formulae.rb +16 -17
- data/src/simplify/replace_indirects_with_references.rb +26 -21
- data/src/simplify/replace_named_references.rb +26 -31
- data/src/simplify/replace_offsets_with_references.rb +33 -34
- data/src/simplify/replace_ranges_with_array_literals.rb +48 -20
- data/src/simplify/replace_shared_strings.rb +15 -13
- data/src/simplify/replace_string_join_on_ranges.rb +7 -9
- data/src/simplify/replace_table_references.rb +16 -11
- data/src/simplify/replace_values_with_constants.rb +6 -4
- data/src/simplify/simplify_arithmetic.rb +33 -19
- data/src/simplify/sort_into_calculation_order.rb +13 -13
- data/src/simplify/wrap_formulae_that_return_arrays_and_are_not_in_arrays.rb +21 -13
- 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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
17
|
-
|
18
|
-
|
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
|
-
|
30
|
-
|
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
|
35
|
-
|
49
|
+
return unless @reference
|
50
|
+
@reference << string
|
36
51
|
end
|
37
|
-
|
52
|
+
|
38
53
|
end
|
@@ -1,10 +1,23 @@
|
|
1
|
-
|
1
|
+
require 'nokogiri'
|
2
2
|
|
3
|
-
class ExtractRelationships <
|
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
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
output
|
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
|
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
|
-
|
1
|
+
require 'nokogiri'
|
2
2
|
|
3
|
-
class ExtractSharedStrings <
|
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
|
-
|
18
|
+
@current = [] if name == "si"
|
7
19
|
end
|
8
20
|
|
9
21
|
def end_element(name)
|
10
22
|
return unless name == "si"
|
11
|
-
|
12
|
-
|
23
|
+
@output << [:string, @current.join]
|
24
|
+
@current = nil
|
13
25
|
end
|
14
26
|
|
15
27
|
def characters(string)
|
16
|
-
return unless
|
17
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
1
|
+
require 'nokogiri'
|
2
2
|
|
3
|
-
class ExtractTable <
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
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
|
35
|
+
@output[@table_name] = [@sheet_name, @table_ref, @table_total_rows, *@table_columns]
|
23
36
|
end
|
24
37
|
end
|