excel_to_code 0.2.19 → 0.2.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/src/commands.rb +2 -1
- data/src/commands/excel_to_test.rb +137 -0
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/excel_to_c_runtime.c +150 -89
- data/src/compile/c/excel_to_c_runtime_test.c +14 -0
- data/src/excel/excel_functions/less_than.rb +21 -7
- data/src/excel/excel_functions/less_than_or_equal.rb +3 -18
- data/src/excel/excel_functions/more_than.rb +25 -8
- data/src/excel/excel_functions/more_than_or_equal.rb +3 -18
- data/src/excel/excel_functions/sumifs.rb +7 -1
- data/src/excel_to_code.rb +1 -1
- data/src/simplify.rb +1 -1
- data/src/simplify/map_formulae_to_values.rb +76 -22
- metadata +3 -3
- data/src/simplify/replace_formulae_with_calculated_values.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8683fda68a9053d3873f3ae6612dc61af4ad4393
|
4
|
+
data.tar.gz: 5ce67a7a836a8931a2b41f7f790ca07b9c565960
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abef9a443559d67c2dd453d6c386febc7516c2fa88b13c2a0a8e43e932809d90d51e400e637bf4845d85bd6296136805c42e762b1d7dc563192ba0b32841f93d
|
7
|
+
data.tar.gz: 6d74e0cfe7dcdad64037700c137066f21afddc10f2acafb2df63c8348e26ea0618e1b321697af7b1024853949bbc71ef1bfd87edb0839e7c75e039ae5d7d36d3
|
data/src/commands.rb
CHANGED
@@ -0,0 +1,137 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require_relative 'excel_to_x'
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
class ExcelToTest < ExcelToX
|
6
|
+
|
7
|
+
attr_accessor :test_name
|
8
|
+
|
9
|
+
def language
|
10
|
+
"C"
|
11
|
+
end
|
12
|
+
|
13
|
+
def go!
|
14
|
+
# This sorts out the settings
|
15
|
+
set_defaults
|
16
|
+
|
17
|
+
log.info "Excel to Code version #{ExcelToCode.version}\n\n"
|
18
|
+
|
19
|
+
# These turn the excel into xml on disk
|
20
|
+
sort_out_output_directories
|
21
|
+
unzip_excel
|
22
|
+
|
23
|
+
# These gets the named references, worksheet names and shared strings out of the excel
|
24
|
+
extract_data_from_workbook
|
25
|
+
|
26
|
+
# This checks that the user inputs of which cells to keep are in the right
|
27
|
+
# format and refer to sheets and references that actually exist
|
28
|
+
clean_cells_that_can_be_set_at_runtime
|
29
|
+
clean_cells_to_keep
|
30
|
+
clean_named_references_to_keep
|
31
|
+
clean_named_references_that_can_be_set_at_runtime
|
32
|
+
|
33
|
+
# This turns named references that are specified as getters and setters
|
34
|
+
# into a series of required cell references
|
35
|
+
transfer_named_references_to_keep_into_cells_to_keep
|
36
|
+
transfer_named_references_that_can_be_set_at_runtime_into_cells_that_can_be_set_at_runtime
|
37
|
+
|
38
|
+
# This makes sure we only extract values from the worksheets we actuall care about
|
39
|
+
extractor = ExtractDataFromWorksheet.new
|
40
|
+
extractor.only_extract_values = true
|
41
|
+
|
42
|
+
cells_to_keep.each do |name, refs|
|
43
|
+
xml_filename = @worksheet_xmls[name]
|
44
|
+
|
45
|
+
log.info "Extracting data from #{name}"
|
46
|
+
xml(xml_filename) do |input|
|
47
|
+
extractor.extract(name, input)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
@values = extractor.values
|
51
|
+
|
52
|
+
|
53
|
+
# These perform some translations to tsimplify the excel
|
54
|
+
# Including:
|
55
|
+
# * Turning row and column references (e.g., A:A) to areas, based on the size of the worksheet
|
56
|
+
# * Turning range references (e.g., A1:B2) into array litterals (e.g., {A1,B1;A2,B2})
|
57
|
+
# * Turning shared formulae into a series of conventional formulae
|
58
|
+
# * Turning array formulae into a series of conventional formulae
|
59
|
+
# * Mergining all the different types of formulae and values into a single hash
|
60
|
+
rewrite_values_to_remove_shared_strings
|
61
|
+
|
62
|
+
# FIXME: Bodge for the moment
|
63
|
+
@formulae = @values
|
64
|
+
create_sorted_references_to_test
|
65
|
+
|
66
|
+
# This actually creates the code (implemented in subclasses)
|
67
|
+
write_code
|
68
|
+
|
69
|
+
# These compile and run the code version of the excel (implemented in subclasses)
|
70
|
+
run_tests
|
71
|
+
|
72
|
+
log.info "The generated code is available in #{File.join(output_directory)}"
|
73
|
+
end
|
74
|
+
|
75
|
+
# These actually create the code version of the excel
|
76
|
+
def write_code
|
77
|
+
write_tests
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_code_to_set_values
|
81
|
+
log.info "Writing code to set values to match those in the worksheet"
|
82
|
+
end
|
83
|
+
|
84
|
+
# FIXME: Should make a Rakefile, especially in order to make sure the dynamic library name
|
85
|
+
|
86
|
+
def write_tests
|
87
|
+
log.info "Writing tests"
|
88
|
+
|
89
|
+
name = output_name.downcase
|
90
|
+
@test_name = "test_#{name}_#{Time.now.to_i}.rb"
|
91
|
+
o = output(@test_name)
|
92
|
+
o.puts "# coding: utf-8"
|
93
|
+
o.puts "# Test for #{name}"
|
94
|
+
o.puts "require 'minitest/autorun'"
|
95
|
+
o.puts "require_relative '#{output_name.downcase}'"
|
96
|
+
o.puts
|
97
|
+
o.puts "class Test#{ruby_module_name} < Minitest::Unit::TestCase"
|
98
|
+
o.puts " def self.runnable_methods"
|
99
|
+
o.puts " puts 'Overriding minitest to run tests in a defined order'"
|
100
|
+
o.puts " methods = methods_matching(/^test_/)"
|
101
|
+
o.puts " end"
|
102
|
+
o.puts " def worksheet; @worksheet ||= init_spreadsheet; end"
|
103
|
+
|
104
|
+
# We need to set up the worksheet with the correct starting values
|
105
|
+
o.puts " def init_spreadsheet"
|
106
|
+
o.puts " s = #{ruby_module_name}Shim.new"
|
107
|
+
|
108
|
+
mapper = MapValuesToRuby.new
|
109
|
+
|
110
|
+
cells_that_can_be_set_at_runtime.each do |sheet, cells|
|
111
|
+
worksheet_c_name = c_name_for_worksheet_name(sheet)
|
112
|
+
cells.each do |cell|
|
113
|
+
ast = @values[[sheet.to_sym, cell.to_sym]]
|
114
|
+
next unless ast
|
115
|
+
value = mapper.map(ast)
|
116
|
+
|
117
|
+
full_reference = worksheet_c_name.length > 0 ? "#{worksheet_c_name}_#{cell.downcase}" : "#{cell.downcase}"
|
118
|
+
o.puts " s.#{full_reference} = #{value}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
o.puts " return s"
|
123
|
+
o.puts " end"
|
124
|
+
|
125
|
+
CompileToCUnitTest.rewrite(Hash[@references_to_test_array], sloppy_tests, @worksheet_c_names, @constants, o)
|
126
|
+
o.puts "end"
|
127
|
+
close(o)
|
128
|
+
puts "New test is in #{File.join(output_directory, @test_name)}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def run_tests
|
132
|
+
return unless actually_run_tests
|
133
|
+
puts "Running the resulting tests"
|
134
|
+
puts `cd #{File.join(output_directory)}; ruby "#{@test_name}"`
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/src/compile/c/a.out
CHANGED
Binary file
|
@@ -1066,113 +1066,174 @@ static ExcelValue iferror(ExcelValue value, ExcelValue value_if_error) {
|
|
1066
1066
|
return value;
|
1067
1067
|
}
|
1068
1068
|
|
1069
|
+
// Order is TRUE, FALSE, String, Number; Blank is zero
|
1069
1070
|
static ExcelValue more_than(ExcelValue a_v, ExcelValue b_v) {
|
1070
|
-
|
1071
|
-
|
1071
|
+
CHECK_FOR_PASSED_ERROR(a_v)
|
1072
|
+
CHECK_FOR_PASSED_ERROR(b_v)
|
1072
1073
|
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1074
|
+
if(a_v.type == ExcelEmpty) { a_v = ZERO; }
|
1075
|
+
if(b_v.type == ExcelEmpty) { b_v = ZERO; }
|
1076
|
+
|
1077
|
+
switch (a_v.type) {
|
1078
|
+
case ExcelString:
|
1079
|
+
switch (b_v.type) {
|
1080
|
+
case ExcelString:
|
1081
|
+
if(strcasecmp(a_v.string,b_v.string) <= 0 ) {return FALSE;} else {return TRUE;}
|
1082
|
+
case ExcelNumber:
|
1083
|
+
return TRUE;
|
1084
|
+
case ExcelBoolean:
|
1085
|
+
return FALSE;
|
1086
|
+
// Following shouldn't happen
|
1087
|
+
case ExcelEmpty:
|
1088
|
+
case ExcelError:
|
1089
|
+
case ExcelRange:
|
1090
|
+
return NA;
|
1091
|
+
}
|
1092
|
+
case ExcelBoolean:
|
1093
|
+
switch (b_v.type) {
|
1094
|
+
case ExcelBoolean:
|
1095
|
+
if(a_v.number == true) {
|
1096
|
+
if (b_v.number == true) { return FALSE; } else { return TRUE; }
|
1097
|
+
} else { // a_v == FALSE
|
1098
|
+
return FALSE;
|
1099
|
+
}
|
1100
|
+
case ExcelString:
|
1101
|
+
case ExcelNumber:
|
1102
|
+
return TRUE;
|
1103
|
+
// Following shouldn't happen
|
1104
|
+
case ExcelEmpty:
|
1105
|
+
case ExcelError:
|
1106
|
+
case ExcelRange:
|
1107
|
+
return NA;
|
1108
|
+
}
|
1109
|
+
case ExcelNumber:
|
1110
|
+
switch (b_v.type) {
|
1111
|
+
case ExcelNumber:
|
1112
|
+
if(a_v.number > b_v.number) { return TRUE; } else { return FALSE; }
|
1113
|
+
case ExcelString:
|
1114
|
+
case ExcelBoolean:
|
1115
|
+
return FALSE;
|
1116
|
+
// Following shouldn't happen
|
1117
|
+
case ExcelEmpty:
|
1118
|
+
case ExcelError:
|
1119
|
+
case ExcelRange:
|
1120
|
+
return NA;
|
1121
|
+
}
|
1122
|
+
// Following shouldn't happen
|
1123
|
+
case ExcelEmpty:
|
1124
|
+
case ExcelError:
|
1125
|
+
case ExcelRange:
|
1126
|
+
return NA;
|
1127
|
+
}
|
1128
|
+
// Shouldn't reach here
|
1129
|
+
return NA;
|
1094
1130
|
}
|
1095
1131
|
|
1096
1132
|
static ExcelValue more_than_or_equal(ExcelValue a_v, ExcelValue b_v) {
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
case ExcelString:
|
1110
|
-
if(b_v.type == ExcelString) {
|
1111
|
-
if(strcasecmp(a_v.string,b_v.string) < 0 ) return FALSE;
|
1112
|
-
return TRUE;
|
1113
|
-
}
|
1114
|
-
return FALSE;
|
1115
|
-
case ExcelError:
|
1116
|
-
return a_v;
|
1117
|
-
case ExcelRange:
|
1118
|
-
return NA;
|
1133
|
+
ExcelValue opposite = less_than(a_v, b_v);
|
1134
|
+
switch (opposite.type) {
|
1135
|
+
case ExcelBoolean:
|
1136
|
+
if(opposite.number == true) { return FALSE; } else { return TRUE; }
|
1137
|
+
case ExcelError:
|
1138
|
+
return opposite;
|
1139
|
+
// Shouldn't reach below
|
1140
|
+
case ExcelNumber:
|
1141
|
+
case ExcelString:
|
1142
|
+
case ExcelEmpty:
|
1143
|
+
case ExcelRange:
|
1144
|
+
return NA;
|
1119
1145
|
}
|
1120
|
-
return FALSE;
|
1121
1146
|
}
|
1122
1147
|
|
1123
|
-
|
1148
|
+
// Order is TRUE, FALSE, String, Number; Blank is zero
|
1124
1149
|
static ExcelValue less_than(ExcelValue a_v, ExcelValue b_v) {
|
1125
1150
|
CHECK_FOR_PASSED_ERROR(a_v)
|
1126
1151
|
CHECK_FOR_PASSED_ERROR(b_v)
|
1127
1152
|
|
1153
|
+
if(a_v.type == ExcelEmpty) { a_v = ZERO; }
|
1154
|
+
if(b_v.type == ExcelEmpty) { b_v = ZERO; }
|
1155
|
+
|
1128
1156
|
switch (a_v.type) {
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1157
|
+
case ExcelString:
|
1158
|
+
switch (b_v.type) {
|
1159
|
+
case ExcelString:
|
1160
|
+
if(strcasecmp(a_v.string, b_v.string) >= 0 ) {
|
1161
|
+
return FALSE;
|
1162
|
+
} else {
|
1163
|
+
return TRUE;
|
1164
|
+
}
|
1165
|
+
case ExcelNumber:
|
1166
|
+
return FALSE;
|
1167
|
+
case ExcelBoolean:
|
1168
|
+
return TRUE;
|
1169
|
+
// The following shouldn't happen
|
1170
|
+
// FIXME: Should abort if it does
|
1171
|
+
case ExcelError:
|
1172
|
+
case ExcelRange:
|
1173
|
+
case ExcelEmpty:
|
1174
|
+
return NA;
|
1175
|
+
}
|
1176
|
+
case ExcelNumber:
|
1177
|
+
switch(b_v.type) {
|
1178
|
+
case ExcelNumber:
|
1179
|
+
if(a_v.number < b_v.number) {
|
1180
|
+
return TRUE;
|
1181
|
+
} else {
|
1182
|
+
return FALSE;
|
1183
|
+
}
|
1184
|
+
case ExcelBoolean:
|
1185
|
+
case ExcelString:
|
1186
|
+
return TRUE;
|
1187
|
+
// The following shouldn't happen
|
1188
|
+
// FIXME: Should abort if it does
|
1189
|
+
case ExcelError:
|
1190
|
+
case ExcelRange:
|
1191
|
+
case ExcelEmpty:
|
1192
|
+
return NA;
|
1193
|
+
}
|
1194
|
+
case ExcelBoolean:
|
1195
|
+
switch(b_v.type) {
|
1196
|
+
case ExcelBoolean:
|
1197
|
+
if(a_v.number == true) {
|
1198
|
+
return FALSE;
|
1199
|
+
} else { // a_v.number == false
|
1200
|
+
if(b_v.number == true) {return TRUE;} else {return FALSE;}
|
1201
|
+
}
|
1202
|
+
case ExcelString:
|
1203
|
+
case ExcelNumber:
|
1204
|
+
return FALSE;
|
1205
|
+
// The following shouldn't happen
|
1206
|
+
// FIXME: Should abort if it does
|
1207
|
+
case ExcelError:
|
1208
|
+
case ExcelRange:
|
1209
|
+
case ExcelEmpty:
|
1210
|
+
return NA;
|
1211
|
+
}
|
1212
|
+
// The following shouldn't happen
|
1213
|
+
// FIXME: Should abort if it does
|
1214
|
+
case ExcelError:
|
1215
|
+
case ExcelRange:
|
1216
|
+
case ExcelEmpty:
|
1217
|
+
return VALUE;
|
1147
1218
|
}
|
1148
|
-
|
1219
|
+
// Shouldn't reach here
|
1220
|
+
return NA;
|
1149
1221
|
}
|
1150
1222
|
|
1151
1223
|
static ExcelValue less_than_or_equal(ExcelValue a_v, ExcelValue b_v) {
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
case ExcelString:
|
1165
|
-
if(b_v.type == ExcelString) {
|
1166
|
-
if(strcasecmp(a_v.string,b_v.string) > 0 ) return FALSE;
|
1167
|
-
return TRUE;
|
1168
|
-
}
|
1169
|
-
return FALSE;
|
1170
|
-
case ExcelError:
|
1171
|
-
return a_v;
|
1172
|
-
case ExcelRange:
|
1173
|
-
return NA;
|
1224
|
+
ExcelValue opposite = more_than(a_v, b_v);
|
1225
|
+
switch (opposite.type) {
|
1226
|
+
case ExcelBoolean:
|
1227
|
+
if(opposite.number == true) { return FALSE; } else { return TRUE; }
|
1228
|
+
case ExcelError:
|
1229
|
+
return opposite;
|
1230
|
+
// Shouldn't reach below
|
1231
|
+
case ExcelNumber:
|
1232
|
+
case ExcelString:
|
1233
|
+
case ExcelEmpty:
|
1234
|
+
case ExcelRange:
|
1235
|
+
return VALUE;
|
1174
1236
|
}
|
1175
|
-
return FALSE;
|
1176
1237
|
}
|
1177
1238
|
|
1178
1239
|
static ExcelValue subtract(ExcelValue a_v, ExcelValue b_v) {
|
@@ -154,6 +154,13 @@ int test_functions() {
|
|
154
154
|
assert(more_than(BLANK,new_excel_number(-1)).number == true);
|
155
155
|
assert(more_than(ONE,BLANK).number == true);
|
156
156
|
assert(more_than(new_excel_number(-1),BLANK).number == false);
|
157
|
+
// .. of different types
|
158
|
+
assert(more_than(TRUE,new_excel_string("Hello")).number == true);
|
159
|
+
assert(more_than(FALSE,new_excel_string("Hello")).number == true);
|
160
|
+
assert(more_than(new_excel_string("Hello"), ONE).number == true);
|
161
|
+
assert(more_than(ONE,new_excel_string("Hello")).number == false);
|
162
|
+
assert(more_than(new_excel_string("Hello"), TRUE).number == false);
|
163
|
+
assert(more_than(new_excel_string("Hello"), FALSE).number == false);
|
157
164
|
|
158
165
|
// Test less than on
|
159
166
|
// .. numbers
|
@@ -174,6 +181,13 @@ int test_functions() {
|
|
174
181
|
assert(less_than(BLANK,new_excel_number(-1)).number == false);
|
175
182
|
assert(less_than(ONE,BLANK).number == false);
|
176
183
|
assert(less_than(new_excel_number(-1),BLANK).number == true);
|
184
|
+
// .. of different types
|
185
|
+
assert(less_than(TRUE,new_excel_string("Hello")).number == false);
|
186
|
+
assert(less_than(FALSE,new_excel_string("Hello")).number == false);
|
187
|
+
assert(less_than(new_excel_string("Hello"), ONE).number == false);
|
188
|
+
assert(less_than(ONE,new_excel_string("Hello")).number == true);
|
189
|
+
assert(less_than(new_excel_string("Hello"), TRUE).number == true);
|
190
|
+
assert(less_than(new_excel_string("Hello"), FALSE).number == true);
|
177
191
|
|
178
192
|
// Test FIND function
|
179
193
|
// ... should find the first occurrence of one string in another, returning :value if the string doesn't match
|
@@ -13,13 +13,27 @@ module ExcelFunctions
|
|
13
13
|
|
14
14
|
case a
|
15
15
|
when String
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
case b
|
17
|
+
when String
|
18
|
+
a.downcase < b.downcase
|
19
|
+
when Numeric
|
20
|
+
false
|
21
|
+
when TrueClass, FalseClass
|
22
|
+
true
|
23
|
+
end
|
24
|
+
when TrueClass
|
25
|
+
false
|
26
|
+
when FalseClass
|
27
|
+
b.is_a?(TrueClass)
|
28
|
+
when Numeric
|
29
|
+
case b
|
30
|
+
when Numeric
|
31
|
+
a < b
|
32
|
+
when String
|
33
|
+
true
|
34
|
+
when TrueClass, FalseClass
|
35
|
+
true
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
25
39
|
|
@@ -3,24 +3,9 @@ require_relative 'apply_to_range'
|
|
3
3
|
module ExcelFunctions
|
4
4
|
|
5
5
|
def less_than_or_equal?(a,b)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return b if b.is_a?(Symbol)
|
10
|
-
|
11
|
-
a = 0 if a == nil
|
12
|
-
b = 0 if b == nil
|
13
|
-
|
14
|
-
case a
|
15
|
-
when String
|
16
|
-
a.downcase <= b.downcase
|
17
|
-
when TrueClass, FalseClass
|
18
|
-
a = a ? 1 : 0
|
19
|
-
b = b ? 1 : 0 if (b.is_a?(TrueClass) || b.is_a?(FalseClass))
|
20
|
-
a <= b
|
21
|
-
else
|
22
|
-
a <= b
|
23
|
-
end
|
6
|
+
opposite = more_than?(a,b)
|
7
|
+
return opposite if opposite.is_a?(Symbol)
|
8
|
+
!opposite
|
24
9
|
end
|
25
10
|
|
26
11
|
end
|
@@ -10,16 +10,33 @@ module ExcelFunctions
|
|
10
10
|
|
11
11
|
a = 0 if a == nil
|
12
12
|
b = 0 if b == nil
|
13
|
-
|
13
|
+
|
14
14
|
case a
|
15
15
|
when String
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
case b
|
17
|
+
when String
|
18
|
+
a.downcase > b.downcase
|
19
|
+
when Numeric
|
20
|
+
true
|
21
|
+
when TrueClass, FalseClass
|
22
|
+
false
|
23
|
+
end
|
24
|
+
when TrueClass
|
25
|
+
!b.is_a?(TrueClass)
|
26
|
+
when FalseClass
|
27
|
+
case b
|
28
|
+
when TrueClass, FalseClass
|
29
|
+
false
|
30
|
+
else
|
31
|
+
true
|
32
|
+
end
|
33
|
+
when Numeric
|
34
|
+
case b
|
35
|
+
when Numeric
|
36
|
+
a > b
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
23
40
|
end
|
24
41
|
end
|
25
42
|
|
@@ -3,24 +3,9 @@ require_relative 'apply_to_range'
|
|
3
3
|
module ExcelFunctions
|
4
4
|
|
5
5
|
def more_than_or_equal?(a,b)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
return b if b.is_a?(Symbol)
|
10
|
-
|
11
|
-
a = 0 if a == nil
|
12
|
-
b = 0 if b == nil
|
13
|
-
|
14
|
-
case a
|
15
|
-
when String
|
16
|
-
a.downcase >= b.downcase
|
17
|
-
when TrueClass, FalseClass
|
18
|
-
a = a ? 1 : 0
|
19
|
-
b = b ? 1 : 0 if (b.is_a?(TrueClass) || b.is_a?(FalseClass))
|
20
|
-
a >= b
|
21
|
-
else
|
22
|
-
a >= b
|
23
|
-
end
|
6
|
+
opposite = less_than?(a,b)
|
7
|
+
return opposite if opposite.is_a?(Symbol)
|
8
|
+
!opposite
|
24
9
|
end
|
25
10
|
|
26
11
|
end
|
@@ -5,6 +5,12 @@ module ExcelFunctions
|
|
5
5
|
range = [range] unless range.is_a?(Array)
|
6
6
|
range = range.flatten
|
7
7
|
|
8
|
+
indexes = _filtered_range_indexes(range, *criteria)
|
9
|
+
return indexes if indexes.is_a?(Symbol)
|
10
|
+
range.values_at(*indexes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def _filtered_range_indexes(range, *criteria)
|
8
14
|
# Sort out the criteria
|
9
15
|
0.step(criteria.length-1,2).each do |i|
|
10
16
|
if criteria[i].is_a?(Array)
|
@@ -59,7 +65,7 @@ module ExcelFunctions
|
|
59
65
|
break unless pass
|
60
66
|
end # criteria loop
|
61
67
|
|
62
|
-
filtered <<
|
68
|
+
filtered << index if pass
|
63
69
|
end
|
64
70
|
|
65
71
|
return filtered
|
data/src/excel_to_code.rb
CHANGED
data/src/simplify.rb
CHANGED
@@ -3,7 +3,7 @@ require_relative "simplify/replace_named_references"
|
|
3
3
|
require_relative "simplify/replace_table_references"
|
4
4
|
require_relative "simplify/replace_ranges_with_array_literals"
|
5
5
|
require_relative "simplify/inline_formulae"
|
6
|
-
require_relative "simplify/
|
6
|
+
require_relative "simplify/map_formulae_to_values"
|
7
7
|
require_relative "simplify/replace_indirects_with_references"
|
8
8
|
require_relative "simplify/replace_offsets_with_references"
|
9
9
|
require_relative "simplify/replace_column_with_column_number"
|
@@ -181,12 +181,15 @@ class MapFormulaeToValues
|
|
181
181
|
normal_function(ast,nil)
|
182
182
|
end
|
183
183
|
|
184
|
+
OK_CHECK_RANGE_TYPES = [:sheet_reference, :cell, :area, :array, :number, :string, :boolean_true, :boolean_false]
|
185
|
+
|
184
186
|
def map_sumifs(ast)
|
185
187
|
values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
|
186
|
-
return if values.
|
187
|
-
return
|
188
|
+
return if values.all? { |a| a == :not_a_value } # Nothing to be done
|
189
|
+
return attempt_to_reduce_sumifs(ast) if values.any? { |a| a == :not_a_value } # Maybe a reduction to be done
|
190
|
+
return unless OK_CHECK_RANGE_TYPES.include?(ast[2].first)
|
188
191
|
sum_value = value(ast[2])
|
189
|
-
if sum_value == :not_a_value
|
192
|
+
if sum_value == :not_a_value # i.e., a sheet_reference, :cell or :area
|
190
193
|
partially_map_sumifs(ast)
|
191
194
|
else
|
192
195
|
ast.replace(formula_value( ast[1], sum_value, *values))
|
@@ -195,27 +198,58 @@ class MapFormulaeToValues
|
|
195
198
|
|
196
199
|
def partially_map_sumifs(ast)
|
197
200
|
values = ast[3..-1].map.with_index { |a,i| value(a, (i % 2) == 0 ? 0 : nil ) }
|
198
|
-
sum_range = []
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
row.each do |cell|
|
203
|
-
next if cell.is_a?(Symbol)
|
204
|
-
sum_range << cell
|
205
|
-
end
|
206
|
-
end
|
201
|
+
sum_range = array_as_values(ast[2]).flatten(1)
|
202
|
+
indexes = @calculator._filtered_range_indexes(sum_range, *values)
|
203
|
+
if indexes.is_a?(Symbol)
|
204
|
+
new_ast = value(filtered_range)
|
207
205
|
else
|
208
|
-
|
206
|
+
new_ast = [:function, :SUM, *sum_range.values_at(*indexes)]
|
207
|
+
end
|
208
|
+
if new_ast != ast
|
209
|
+
@replacements_made_in_the_last_pass += 1
|
210
|
+
ast.replace(new_ast)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# FIXME: Ends up making everything single column. Is that ok?!
|
215
|
+
def attempt_to_reduce_sumifs(ast)
|
216
|
+
return unless OK_CHECK_RANGE_TYPES.include?(ast[2].first)
|
217
|
+
# First combine into a series of checks
|
218
|
+
criteria_that_can_be_resolved = []
|
219
|
+
criteria_that_cant_be_resolved = []
|
220
|
+
ast[3..-1].each_slice(2) do |check|
|
221
|
+
# Give up unless we have something that can actually be used
|
222
|
+
return unless OK_CHECK_RANGE_TYPES.include?(check[0].first)
|
223
|
+
check_range_value = value(check[0])
|
224
|
+
check_criteria_value = value(check[1])
|
225
|
+
if check_range_value == :not_a_value || check_criteria_value == :not_a_value
|
226
|
+
criteria_that_cant_be_resolved << check
|
227
|
+
else
|
228
|
+
criteria_that_can_be_resolved << [check_range_value, check_criteria_value]
|
229
|
+
end
|
209
230
|
end
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
231
|
+
return if criteria_that_can_be_resolved.empty?
|
232
|
+
sum_range = array_as_values(ast[2]).flatten(1)
|
233
|
+
indexes = @calculator._filtered_range_indexes(sum_range, *criteria_that_can_be_resolved.flatten(1))
|
234
|
+
if indexes.is_a?(Symbol)
|
235
|
+
return
|
236
|
+
elsif indexes.empty?
|
237
|
+
new_ast = [:number, 0]
|
214
238
|
else
|
215
|
-
|
239
|
+
new_ast = [:function, :SUMIFS]
|
240
|
+
new_ast << ast_for_array(sum_range.values_at(*indexes))
|
241
|
+
criteria_that_cant_be_resolved.each do |check|
|
242
|
+
new_ast << ast_for_array(array_as_values(check.first).flatten(1).values_at(*indexes))
|
243
|
+
new_ast << check.last
|
244
|
+
end
|
245
|
+
end
|
246
|
+
if new_ast != ast
|
247
|
+
@replacements_made_in_the_last_pass += 1
|
248
|
+
ast.replace(new_ast)
|
216
249
|
end
|
217
250
|
end
|
218
251
|
|
252
|
+
|
219
253
|
# [:function, "COUNT", range]
|
220
254
|
def map_count(ast)
|
221
255
|
values = ast[2..-1].map { |a| value(a, nil) }
|
@@ -274,17 +308,32 @@ class MapFormulaeToValues
|
|
274
308
|
not_number_array.concat(result.last)
|
275
309
|
end
|
276
310
|
if number_total == 0 && not_number_array.empty?
|
277
|
-
|
311
|
+
new_ast = [:number, number_total]
|
312
|
+
if new_ast != ast
|
313
|
+
@replacements_made_in_the_last_pass += 1
|
314
|
+
ast.replace(new_ast)
|
315
|
+
end
|
278
316
|
# FIXME: Will I be haunted by this? What if doing a sum of something that isn't a number
|
279
317
|
# and so what is expected is a VALUE error?. YES. This doesn't work well.
|
280
318
|
elsif ast.length == 3 && [:cell, :sheet_reference].include?(ast[2].first)
|
281
|
-
|
319
|
+
new_ast = n(ast[2])
|
320
|
+
if new_ast != ast
|
321
|
+
@replacements_made_in_the_last_pass += 1
|
322
|
+
ast.replace(new_ast)
|
323
|
+
end
|
282
324
|
elsif ast.length == 3 && ast[2][0] == :function && ast[2][1] == :ENSURE_IS_NUMBER
|
283
|
-
ast
|
325
|
+
new_ast = ast[2]
|
326
|
+
if new_ast != ast
|
327
|
+
@replacements_made_in_the_last_pass += 1
|
328
|
+
ast.replace(new_ast)
|
329
|
+
end
|
284
330
|
else
|
285
331
|
new_ast = [:function, :SUM].concat(not_number_array)
|
286
332
|
new_ast.push([:number, number_total]) unless number_total == 0
|
287
|
-
ast
|
333
|
+
if new_ast != ast
|
334
|
+
@replacements_made_in_the_last_pass += 1
|
335
|
+
ast.replace(new_ast)
|
336
|
+
end
|
288
337
|
end
|
289
338
|
ast
|
290
339
|
end
|
@@ -346,6 +395,11 @@ class MapFormulaeToValues
|
|
346
395
|
end
|
347
396
|
end
|
348
397
|
|
398
|
+
# FIXME: Assumes single column. Not wise?
|
399
|
+
def ast_for_array(array)
|
400
|
+
[:array,*array.map { |row| [:row, row ]}]
|
401
|
+
end
|
402
|
+
|
349
403
|
ERRORS = {
|
350
404
|
:"#NAME?" => :name,
|
351
405
|
:"#VALUE!" => :value,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: excel_to_code
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Counsell, Green on Black Ltd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubypeg
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- src/commands.rb
|
114
114
|
- src/commands/excel_to_c.rb
|
115
115
|
- src/commands/excel_to_ruby.rb
|
116
|
+
- src/commands/excel_to_test.rb
|
116
117
|
- src/commands/excel_to_x.rb
|
117
118
|
- src/compile.rb
|
118
119
|
- src/compile/c.rb
|
@@ -240,7 +241,6 @@ files:
|
|
240
241
|
- src/simplify/replace_arrays_with_single_cells.rb
|
241
242
|
- src/simplify/replace_column_with_column_number.rb
|
242
243
|
- src/simplify/replace_common_elements_in_formulae.rb
|
243
|
-
- src/simplify/replace_formulae_with_calculated_values.rb
|
244
244
|
- src/simplify/replace_indirects_with_references.rb
|
245
245
|
- src/simplify/replace_named_references.rb
|
246
246
|
- src/simplify/replace_offsets_with_references.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require_relative 'map_formulae_to_values'
|
2
|
-
|
3
|
-
class ReplaceFormulaeWithCalculatedValues
|
4
|
-
|
5
|
-
attr_accessor :excel_file
|
6
|
-
|
7
|
-
def self.replace(*args)
|
8
|
-
self.new.replace(*args)
|
9
|
-
end
|
10
|
-
|
11
|
-
attr_accessor :replacements_made_in_the_last_pass
|
12
|
-
|
13
|
-
def replace(input,output)
|
14
|
-
rewriter = MapFormulaeToValues.new
|
15
|
-
rewriter.original_excel_filename = excel_file
|
16
|
-
input.each_line do |line|
|
17
|
-
begin
|
18
|
-
ref, ast = line.split("\t")
|
19
|
-
output.puts "#{ref}\t#{rewriter.map(eval(ast)).inspect}"
|
20
|
-
rewriter.reset
|
21
|
-
rescue Exception => e
|
22
|
-
puts "Exception at line #{line}"
|
23
|
-
raise
|
24
|
-
end
|
25
|
-
end
|
26
|
-
@replacements_made_in_the_last_pass = rewriter.replacements_made_in_the_last_pass
|
27
|
-
end
|
28
|
-
end
|