excel_to_code 0.2.19 → 0.2.20
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.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
|