excel_to_code 0.2.28 → 0.2.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/src/commands/excel_to_x.rb +37 -23
- data/src/compile/c/a.out +0 -0
- data/src/compile/c/excel_to_c_runtime.c +82 -2
- data/src/compile/c/excel_to_c_runtime_test.c +36 -0
- data/src/compile/c/map_formulae_to_c.rb +2 -0
- data/src/compile/ruby/map_formulae_to_ruby.rb +2 -0
- data/src/excel/excel_functions/index.rb +2 -0
- data/src/excel/excel_functions/iserr.rb +9 -0
- data/src/excel/excel_functions/text.rb +13 -3
- data/src/excel/excel_functions.rb +2 -0
- data/src/excel/formula_peg.rb +1 -1
- data/src/excel/formula_peg.txt +1 -1
- data/src/excel/table.rb +4 -0
- data/src/excel_to_code.rb +1 -1
- data/src/simplify/inline_formulae.rb +14 -7
- data/src/simplify/map_formulae_to_values.rb +6 -0
- data/src/simplify/replace_arithmetic_on_ranges.rb +17 -0
- data/src/simplify/replace_named_references.rb +8 -6
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa747184660d6eb2e3a92bce25849e0683eff073
|
4
|
+
data.tar.gz: a959fd27d7000dc9167a4e63972a544fef83e0a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28b6848296a487e694634a2e208b7b96972d8b7efea4ca7d9eebd5d9c5bf5f667274638f367b757aae365acf32375beb45a2ab5502e900a3cc3ed5243e897732
|
7
|
+
data.tar.gz: 600e18d8bdafd963d5dce0e626eb7821368855c69bb4df82acb06d618778c09b7283cef1e912c2d23cb9a575c3d65e531e6dec26bb0fd82ae5cb4f18b6e6e659
|
data/README.md
CHANGED
@@ -46,3 +46,5 @@ There are some how to guides in the doc folder.
|
|
46
46
|
5. Sometimes gives cells as being empty, when excel would give the cell as having a numeric value of zero
|
47
47
|
6. The generated C version does not multithread and will give bad results if you try
|
48
48
|
7. Newlines are removed from strings
|
49
|
+
8. The generated code uses floating point, rather than fully precise arithmetic, so results can differ slightly
|
50
|
+
9. The generated code uses the sprintf approach to rounding (even-odd) rather than excel's 0.5 rounds away from zero.
|
data/src/commands/excel_to_x.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require 'fileutils'
|
3
3
|
require 'logger'
|
4
|
+
require 'tmpdir'
|
4
5
|
require_relative '../excel_to_code'
|
5
6
|
|
6
7
|
# FIXME: Correct case for all worksheet references
|
@@ -120,16 +121,6 @@ class ExcelToX
|
|
120
121
|
# cells on tha sheet and nothing else.
|
121
122
|
attr_accessor :isolate
|
122
123
|
|
123
|
-
# Deprecated
|
124
|
-
def run_in_memory=(boolean)
|
125
|
-
$stderr.puts "The run_in_memory switch is deprecated (it is now always true). Please remove calls to it"
|
126
|
-
end
|
127
|
-
|
128
|
-
# Deprecated
|
129
|
-
def intermediate_directory=(dirname)
|
130
|
-
$stderr.puts "The intermediate_directory switch is deprecated (nowdays, no intermediate files are written). Please remove calls to it"
|
131
|
-
end
|
132
|
-
|
133
124
|
# This is the main method. Once all the above attributes have been set, it should be called to actually do the work.
|
134
125
|
def go!
|
135
126
|
# This sorts out the settings
|
@@ -211,7 +202,9 @@ class ExcelToX
|
|
211
202
|
# These compile and run the code version of the excel (implemented in subclasses)
|
212
203
|
compile_code
|
213
204
|
run_tests
|
214
|
-
|
205
|
+
|
206
|
+
cleanup
|
207
|
+
|
215
208
|
log.info "The generated code is available in #{File.join(output_directory)}"
|
216
209
|
end
|
217
210
|
|
@@ -219,8 +212,11 @@ class ExcelToX
|
|
219
212
|
def set_defaults
|
220
213
|
raise ExcelToCodeException.new("No excel file has been specified") unless excel_file
|
221
214
|
|
222
|
-
self.output_directory ||=
|
223
|
-
self.xml_directory
|
215
|
+
self.output_directory ||= Dir.pwd
|
216
|
+
unless self.xml_directory
|
217
|
+
self.xml_directory ||= Dir.mktmpdir
|
218
|
+
@delete_xml_directory_at_end = true
|
219
|
+
end
|
224
220
|
|
225
221
|
self.output_name ||= "Excelspreadsheet"
|
226
222
|
|
@@ -240,7 +236,7 @@ class ExcelToX
|
|
240
236
|
self.extract_repeated_parts_of_formulae = true if @extract_repeated_parts_of_formulae == nil
|
241
237
|
self.should_inline_formulae_that_are_only_used_once = true if @should_inline_formulae_that_are_only_used_once == nil
|
242
238
|
|
243
|
-
# This setting is used for debugging, and makes the system only do the conversion on a subset of the
|
239
|
+
# This setting is used for debugging, and makes the system only do the conversion on a subset of the worksheets
|
244
240
|
if self.isolate
|
245
241
|
self.isolate = [self.isolate] unless self.isolate.is_a?(Array)
|
246
242
|
self.cells_to_keep ||= {}
|
@@ -251,7 +247,6 @@ class ExcelToX
|
|
251
247
|
log.warn "Isolating #{@isolate} worksheet(s). No other sheets will be converted"
|
252
248
|
end
|
253
249
|
end
|
254
|
-
|
255
250
|
|
256
251
|
# Creates any directories that are needed
|
257
252
|
def sort_out_output_directories
|
@@ -503,6 +498,7 @@ class ExcelToX
|
|
503
498
|
@table_rids = extractor.table_rids
|
504
499
|
@tables = {}
|
505
500
|
@table_areas = {}
|
501
|
+
@table_data = {}
|
506
502
|
extract_tables
|
507
503
|
end
|
508
504
|
|
@@ -529,6 +525,7 @@ class ExcelToX
|
|
529
525
|
table = Table.new(table_name, *details)
|
530
526
|
@tables[name] = table
|
531
527
|
@table_areas[name.to_sym] = table.all
|
528
|
+
@table_data[name.to_sym] = table.data
|
532
529
|
end
|
533
530
|
end
|
534
531
|
end
|
@@ -540,6 +537,10 @@ class ExcelToX
|
|
540
537
|
@table_areas.each do |name, reference|
|
541
538
|
@table_areas[name] = @replace_ranges_with_array_literals_replacer.map(reference)
|
542
539
|
end
|
540
|
+
|
541
|
+
@table_data.each do |name, reference|
|
542
|
+
@table_data[name] = @replace_ranges_with_array_literals_replacer.map(reference)
|
543
|
+
end
|
543
544
|
|
544
545
|
end
|
545
546
|
|
@@ -687,7 +688,7 @@ class ExcelToX
|
|
687
688
|
log.info "Expanding #{@formulae_array.size} array formulae"
|
688
689
|
# FIMXE: Refactor this
|
689
690
|
|
690
|
-
named_reference_replacer = ReplaceNamedReferencesAst.new(@named_references)
|
691
|
+
named_reference_replacer = ReplaceNamedReferencesAst.new(@named_references, nil, @table_data)
|
691
692
|
table_reference_replacer = ReplaceTableReferenceAst.new(@tables)
|
692
693
|
@replace_ranges_with_array_literals_replacer ||= ReplaceRangesWithArrayLiteralsAst.new
|
693
694
|
expand_array_formulae_replacer = AstExpandArrayFormulae.new
|
@@ -885,7 +886,7 @@ class ExcelToX
|
|
885
886
|
@shared_string_replacer ||= ReplaceSharedStringAst.new(@shared_strings)
|
886
887
|
@replace_arithmetic_on_ranges_replacer ||= ReplaceArithmeticOnRangesAst.new
|
887
888
|
@wrap_formulae_that_return_arrays_replacer ||= WrapFormulaeThatReturnArraysAndAReNotInArraysAst.new
|
888
|
-
@named_reference_replacer ||= ReplaceNamedReferencesAst.new(@named_references)
|
889
|
+
@named_reference_replacer ||= ReplaceNamedReferencesAst.new(@named_references, nil, @table_data)
|
889
890
|
@table_reference_replacer ||= ReplaceTableReferenceAst.new(@tables)
|
890
891
|
@replace_ranges_with_array_literals_replacer ||= ReplaceRangesWithArrayLiteralsAst.new
|
891
892
|
@replace_arrays_with_single_cells_replacer ||= ReplaceArraysWithSingleCellsAst.new
|
@@ -893,6 +894,8 @@ class ExcelToX
|
|
893
894
|
@sheetless_cell_reference_replacer ||= RewriteCellReferencesToIncludeSheetAst.new
|
894
895
|
@replace_references_to_blanks_with_zeros ||= ReplaceReferencesToBlanksWithZeros.new(@formulae, nil, inline_ast_decision)
|
895
896
|
|
897
|
+
#require 'pry'; binding.pry
|
898
|
+
|
896
899
|
cells.each do |ref, ast|
|
897
900
|
begin
|
898
901
|
@sheetless_cell_reference_replacer.worksheet = ref.first
|
@@ -919,27 +922,32 @@ class ExcelToX
|
|
919
922
|
raise
|
920
923
|
end
|
921
924
|
end
|
925
|
+
#require 'pry'; binding.pry
|
922
926
|
end
|
923
927
|
|
924
928
|
# These types of cells don't conatain formulae and can therefore be skipped
|
925
929
|
VALUE_TYPE = {:number => true, :string => true, :blank => true, :null => true, :error => true, :boolean_true => true, :boolean_false => true}
|
930
|
+
|
931
|
+
def must_keep?(ref)
|
932
|
+
must_keep_in_sheet = @cells_that_can_be_set_at_runtime[ref.first]
|
933
|
+
return false unless must_keep_in_sheet
|
934
|
+
return true if must_keep_in_sheet == :all
|
935
|
+
must_keep_in_sheet.include?(ref.last)
|
936
|
+
end
|
926
937
|
|
927
938
|
def inline_ast_decision
|
928
939
|
@inline_ast_decision ||= lambda do |sheet, cell, references|
|
929
|
-
|
930
|
-
if
|
940
|
+
ref = [sheet,cell]
|
941
|
+
if must_keep?(ref)
|
931
942
|
false
|
932
943
|
else
|
933
|
-
ast = references[
|
944
|
+
ast = references[ref]
|
934
945
|
if ast
|
935
946
|
case ast.first
|
936
947
|
when :number, :string; true
|
937
948
|
when :blank, :null; true
|
938
949
|
when :error; true
|
939
950
|
when :boolean_true, :boolean_false; true
|
940
|
-
when :cell; true
|
941
|
-
when :sheet_reference
|
942
|
-
ast[2][0] != :named_reference
|
943
951
|
else
|
944
952
|
false
|
945
953
|
end
|
@@ -993,6 +1001,7 @@ class ExcelToX
|
|
993
1001
|
inline_replacer.current_sheet_name = [ref.first]
|
994
1002
|
inline_replacer.map(ast)
|
995
1003
|
# If a formula references a cell containing a value, the reference is replaced with the value (e.g., if A1 := 2 and A2 := A1 + 1 then becomes: A2 := 2 + 1)
|
1004
|
+
#require 'pry'; binding.pry if ref == [:"Outputs - Summary table", :E77]
|
996
1005
|
value_replacer.map(ast)
|
997
1006
|
column_and_row_function_replacement.current_reference = ref.last
|
998
1007
|
if column_and_row_function_replacement.replace(ast)
|
@@ -1316,4 +1325,9 @@ class ExcelToX
|
|
1316
1325
|
@ruby_module_name
|
1317
1326
|
end
|
1318
1327
|
|
1328
|
+
def cleanup
|
1329
|
+
log.info "Cleaning up"
|
1330
|
+
FileUtils.remove_entry(self.xml_directory) if @delete_xml_directory_at_end
|
1331
|
+
end
|
1332
|
+
|
1319
1333
|
end
|
data/src/compile/c/a.out
CHANGED
Binary file
|
@@ -4,6 +4,7 @@
|
|
4
4
|
#include <stdlib.h>
|
5
5
|
#include <ctype.h>
|
6
6
|
#include <math.h>
|
7
|
+
#include <locale.h>
|
7
8
|
|
8
9
|
#ifndef NUMBER_OF_REFS
|
9
10
|
#define NUMBER_OF_REFS 0
|
@@ -63,6 +64,7 @@ static ExcelValue find(ExcelValue string_to_look_for_v, ExcelValue string_to_loo
|
|
63
64
|
static ExcelValue hlookup_3(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue row_number_v);
|
64
65
|
static ExcelValue hlookup(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue row_number_v, ExcelValue match_type_v);
|
65
66
|
static ExcelValue iferror(ExcelValue value, ExcelValue value_if_error);
|
67
|
+
static ExcelValue iserr(ExcelValue value);
|
66
68
|
static ExcelValue excel_index(ExcelValue array_v, ExcelValue row_number_v, ExcelValue column_number_v);
|
67
69
|
static ExcelValue excel_index_2(ExcelValue array_v, ExcelValue row_number_v);
|
68
70
|
static ExcelValue excel_isnumber(ExcelValue number);
|
@@ -670,6 +672,9 @@ static ExcelValue excel_index(ExcelValue array_v, ExcelValue row_number_v, Excel
|
|
670
672
|
|
671
673
|
if(row_number > rows) return REF;
|
672
674
|
if(column_number > columns) return REF;
|
675
|
+
|
676
|
+
if(row_number == 0 && rows == 1) row_number = 1;
|
677
|
+
if(column_number == 0 && columns == 1) column_number = 1;
|
673
678
|
|
674
679
|
if(row_number == 0) { // We need the whole column
|
675
680
|
if(column_number < 1) return REF;
|
@@ -1077,6 +1082,19 @@ static ExcelValue iferror(ExcelValue value, ExcelValue value_if_error) {
|
|
1077
1082
|
return value;
|
1078
1083
|
}
|
1079
1084
|
|
1085
|
+
static ExcelValue iserr(ExcelValue value) {
|
1086
|
+
if(value.type == ExcelError) {
|
1087
|
+
if(value.number == NA.number) {
|
1088
|
+
return FALSE;
|
1089
|
+
} else {
|
1090
|
+
return TRUE;
|
1091
|
+
}
|
1092
|
+
} else {
|
1093
|
+
return FALSE;
|
1094
|
+
}
|
1095
|
+
}
|
1096
|
+
|
1097
|
+
|
1080
1098
|
// Order is TRUE, FALSE, String, Number; Blank is zero
|
1081
1099
|
static ExcelValue more_than(ExcelValue a_v, ExcelValue b_v) {
|
1082
1100
|
CHECK_FOR_PASSED_ERROR(a_v)
|
@@ -2015,6 +2033,8 @@ static ExcelValue sumproduct(int number_of_arguments, ExcelValue *arguments) {
|
|
2015
2033
|
return new_excel_number(sum);
|
2016
2034
|
}
|
2017
2035
|
|
2036
|
+
// FIXME: This could do with being done properly, rather than
|
2037
|
+
// on a case by case basis.
|
2018
2038
|
static ExcelValue text(ExcelValue number_v, ExcelValue format_v) {
|
2019
2039
|
CHECK_FOR_PASSED_ERROR(number_v)
|
2020
2040
|
CHECK_FOR_PASSED_ERROR(format_v)
|
@@ -2032,6 +2052,10 @@ static ExcelValue text(ExcelValue number_v, ExcelValue format_v) {
|
|
2032
2052
|
return new_excel_string("");
|
2033
2053
|
}
|
2034
2054
|
|
2055
|
+
if(format_v.type == ExcelNumber && format_v.number == 0) {
|
2056
|
+
format_v = new_excel_string("0");
|
2057
|
+
}
|
2058
|
+
|
2035
2059
|
if(number_v.type == ExcelString) {
|
2036
2060
|
s = number_v.string;
|
2037
2061
|
if (s == NULL || *s == '\0' || isspace(*s)) {
|
@@ -2054,11 +2078,67 @@ static ExcelValue text(ExcelValue number_v, ExcelValue format_v) {
|
|
2054
2078
|
if(strcmp(format_v.string,"0%") == 0) {
|
2055
2079
|
// FIXME: Too little?
|
2056
2080
|
s = malloc(100);
|
2057
|
-
sprintf(s, "%
|
2081
|
+
sprintf(s, "%0.0f%%", number_v.number*100);
|
2082
|
+
free_later(s);
|
2083
|
+
result = new_excel_string(s);
|
2084
|
+
} else if(strcmp(format_v.string,"0.0%") == 0) {
|
2085
|
+
// FIXME: Too little?
|
2086
|
+
s = malloc(100);
|
2087
|
+
sprintf(s, "%0.1f%%", number_v.number*100);
|
2088
|
+
free_later(s);
|
2089
|
+
result = new_excel_string(s);
|
2090
|
+
} else if(strcmp(format_v.string,"0") == 0) {
|
2091
|
+
s = malloc(100);
|
2092
|
+
sprintf(s, "%0.0f",number_v.number);
|
2093
|
+
free_later(s);
|
2094
|
+
result = new_excel_string(s);
|
2095
|
+
} else if(strcmp(format_v.string,"0.0") == 0) {
|
2096
|
+
s = malloc(100);
|
2097
|
+
sprintf(s, "%0.1f",number_v.number);
|
2098
|
+
free_later(s);
|
2099
|
+
result = new_excel_string(s);
|
2100
|
+
} else if(strcmp(format_v.string,"0.00") == 0) {
|
2101
|
+
s = malloc(100);
|
2102
|
+
sprintf(s, "%0.2f",number_v.number);
|
2103
|
+
free_later(s);
|
2104
|
+
result = new_excel_string(s);
|
2105
|
+
} else if(strcmp(format_v.string,"#,##") == 0) {
|
2106
|
+
s = malloc(100);
|
2107
|
+
setlocale(LC_ALL,"");
|
2108
|
+
sprintf(s, "%'0.0f",number_v.number);
|
2109
|
+
free_later(s);
|
2110
|
+
result = new_excel_string(s);
|
2111
|
+
} else if(strcmp(format_v.string,"#,##0") == 0) {
|
2112
|
+
s = malloc(100);
|
2113
|
+
setlocale(LC_ALL,"");
|
2114
|
+
sprintf(s, "%'0.0f",number_v.number);
|
2115
|
+
free_later(s);
|
2116
|
+
result = new_excel_string(s);
|
2117
|
+
} else if(strcmp(format_v.string,"#,##0.0") == 0) {
|
2118
|
+
s = malloc(100);
|
2119
|
+
setlocale(LC_ALL,"");
|
2120
|
+
sprintf(s, "%'0.1f",number_v.number);
|
2121
|
+
free_later(s);
|
2122
|
+
result = new_excel_string(s);
|
2123
|
+
} else if(strcmp(format_v.string,"#,##0.00") == 0) {
|
2124
|
+
s = malloc(100);
|
2125
|
+
setlocale(LC_ALL,"");
|
2126
|
+
sprintf(s, "%'0.2f",number_v.number);
|
2127
|
+
free_later(s);
|
2128
|
+
result = new_excel_string(s);
|
2129
|
+
} else if(strcmp(format_v.string,"#,##0.000") == 0) {
|
2130
|
+
s = malloc(100);
|
2131
|
+
setlocale(LC_ALL,"");
|
2132
|
+
sprintf(s, "%'0.3f",number_v.number);
|
2133
|
+
free_later(s);
|
2134
|
+
result = new_excel_string(s);
|
2135
|
+
} else if(strcmp(format_v.string,"0000") == 0) {
|
2136
|
+
s = malloc(100);
|
2137
|
+
sprintf(s, "%04.0f",number_v.number);
|
2058
2138
|
free_later(s);
|
2059
2139
|
result = new_excel_string(s);
|
2060
2140
|
} else {
|
2061
|
-
return
|
2141
|
+
return new_excel_string("Text format not recognised");
|
2062
2142
|
}
|
2063
2143
|
|
2064
2144
|
// inspect_excel_value(result);
|
@@ -221,6 +221,28 @@ int test_functions() {
|
|
221
221
|
assert(iferror(new_excel_string("ok"),ONE).type == ExcelString);
|
222
222
|
assert(iferror(VALUE,ONE).type == ExcelNumber);
|
223
223
|
|
224
|
+
// Test the ISERR function
|
225
|
+
assert(iserr(NA).type == ExcelBoolean);
|
226
|
+
assert(iserr(NA).number == 0);
|
227
|
+
assert(iserr(DIV0).type == ExcelBoolean);
|
228
|
+
assert(iserr(DIV0).type == ExcelBoolean);
|
229
|
+
assert(iserr(REF).number == 1);
|
230
|
+
assert(iserr(REF).type == ExcelBoolean);
|
231
|
+
assert(iserr(VALUE).number == 1);
|
232
|
+
assert(iserr(VALUE).type == ExcelBoolean);
|
233
|
+
assert(iserr(NAME).number == 1);
|
234
|
+
assert(iserr(NAME).number == 1);
|
235
|
+
assert(iserr(BLANK).type == ExcelBoolean);
|
236
|
+
assert(iserr(BLANK).type == ExcelBoolean);
|
237
|
+
assert(iserr(TRUE).type == ExcelBoolean);
|
238
|
+
assert(iserr(TRUE).type == ExcelBoolean);
|
239
|
+
assert(iserr(FALSE).number == 0);
|
240
|
+
assert(iserr(FALSE).number == 0);
|
241
|
+
assert(iserr(ONE).number == 0);
|
242
|
+
assert(iserr(ONE).number == 0);
|
243
|
+
assert(iserr(new_excel_string("Hello")).number == 0);
|
244
|
+
assert(iserr(new_excel_string("Hello")).number == 0);
|
245
|
+
|
224
246
|
// Test the INDEX function
|
225
247
|
ExcelValue index_array_1[] = { new_excel_number(10), new_excel_number(20), BLANK };
|
226
248
|
ExcelValue index_array_1_v_column = new_excel_range(index_array_1,3,1);
|
@@ -268,6 +290,10 @@ int test_functions() {
|
|
268
290
|
assert(excel_index(index_array_2_v,BLANK,new_excel_number(2.0)).type == ExcelRange);
|
269
291
|
// ... it should return an error if an argument is an error
|
270
292
|
assert(excel_index(NA,NA,NA).type == ExcelError);
|
293
|
+
// ... it should return a single value if single column and passed zero as column number
|
294
|
+
assert(excel_index(index_array_1_v_column,new_excel_number(2.0), ZERO).number == 20);
|
295
|
+
assert(excel_index(index_array_1_v_row,ZERO, new_excel_number(2.0)).number == 20);
|
296
|
+
|
271
297
|
|
272
298
|
// LEFT(string,[characters])
|
273
299
|
// ... should return the left n characters from a string
|
@@ -672,9 +698,19 @@ int test_functions() {
|
|
672
698
|
// Test TEXT
|
673
699
|
assert(strcmp(text(new_excel_number(1.0), new_excel_string("0%")).string, "100%") == 0);
|
674
700
|
assert(strcmp(text(new_excel_string("1"), new_excel_string("0%")).string, "100%") == 0);
|
701
|
+
assert(strcmp(text(new_excel_string("0.00251"), new_excel_string("0.0%")).string, "0.3%") == 0);
|
675
702
|
assert(strcmp(text(BLANK, new_excel_string("0%")).string, "0%") == 0);
|
676
703
|
assert(strcmp(text(new_excel_number(1.0), BLANK).string, "") == 0);
|
677
704
|
assert(strcmp(text(new_excel_string("ASGASD"), new_excel_string("0%")).string, "ASGASD") == 0);
|
705
|
+
assert(strcmp(text(new_excel_number(1.1518), new_excel_string("0")).string, "1") == 0);
|
706
|
+
assert(strcmp(text(new_excel_number(1.1518), ZERO).string, "1") == 0);
|
707
|
+
assert(strcmp(text(new_excel_number(1.1518), new_excel_string("0.0")).string, "1.2") == 0);
|
708
|
+
assert(strcmp(text(new_excel_number(1.1518), new_excel_string("0.00")).string, "1.15") == 0);
|
709
|
+
assert(strcmp(text(new_excel_number(12.51), new_excel_string("0000")).string, "0013") == 0);
|
710
|
+
assert(strcmp(text(new_excel_number(125101), new_excel_string("0000")).string, "125101") == 0);
|
711
|
+
assert(strcmp(text(new_excel_number(123456789.123456), new_excel_string("#,##")).string, "123,456,789") == 0);
|
712
|
+
assert(strcmp(text(new_excel_number(123456789.123456), new_excel_string("#,##0")).string, "123,456,789") == 0);
|
713
|
+
assert(strcmp(text(new_excel_number(123456789.123456), new_excel_string("#,##0.0")).string, "123,456,789.1") == 0);
|
678
714
|
|
679
715
|
// Test LOG
|
680
716
|
// One argument variant assumes LOG base 10
|
@@ -46,6 +46,7 @@ class MapFormulaeToC < MapValuesToC
|
|
46
46
|
:'IF2' => 'excel_if_2',
|
47
47
|
:'IF3' => 'excel_if',
|
48
48
|
:'IFERROR' => 'iferror',
|
49
|
+
:'ISERR' => 'iserr',
|
49
50
|
:'INDEX2' => 'excel_index_2',
|
50
51
|
:'INDEX3' => 'excel_index',
|
51
52
|
:'INT' => 'excel_int',
|
@@ -56,6 +57,7 @@ class MapFormulaeToC < MapValuesToC
|
|
56
57
|
:'LEFT2' => 'left',
|
57
58
|
:'LEN' => 'len',
|
58
59
|
:'LN' => 'ln',
|
60
|
+
:'LOG10' => 'excel_log',
|
59
61
|
:'LOG1' => 'excel_log',
|
60
62
|
:'LOG2' => 'excel_log_2',
|
61
63
|
:'MATCH2' => 'excel_match_2',
|
@@ -36,12 +36,14 @@ class MapFormulaeToRuby < MapValuesToRuby
|
|
36
36
|
:'INDEX' => 'index',
|
37
37
|
:'INT' => 'int',
|
38
38
|
:'ISBLANK' => 'isblank',
|
39
|
+
:'ISERR' => 'iserr',
|
39
40
|
:'ISNUMBER' => 'isnumber',
|
40
41
|
:'LARGE' => 'large',
|
41
42
|
:'LEFT' => 'left',
|
42
43
|
:'LEN' => 'len',
|
43
44
|
:'LN' => 'ln',
|
44
45
|
:'LOG' => 'log',
|
46
|
+
:'LOG10' => 'log',
|
45
47
|
:'LOWER' => 'lower',
|
46
48
|
:'MATCH' => 'excel_match',
|
47
49
|
:'MAX' => 'max',
|
@@ -37,12 +37,14 @@ module ExcelFunctions
|
|
37
37
|
def index_for_whole_row(array,row_number)
|
38
38
|
return :ref if row_number < 1
|
39
39
|
return :ref if row_number > array.length
|
40
|
+
return index_for_row_column(array, row_number, 1) if array.first.length == 1
|
40
41
|
[array[row_number-1]]
|
41
42
|
end
|
42
43
|
|
43
44
|
def index_for_whole_column(array,column_number)
|
44
45
|
return :ref if column_number < 1
|
45
46
|
return :ref if column_number > array[0].length
|
47
|
+
return index_for_row_column(array, 1, column_number) if array.length == 1
|
46
48
|
array.map { |row| [row[column_number-1]]}
|
47
49
|
end
|
48
50
|
end
|
@@ -3,6 +3,7 @@ module ExcelFunctions
|
|
3
3
|
def text(number, format)
|
4
4
|
number ||= 0
|
5
5
|
return "" unless format
|
6
|
+
format = "0" if format == 0.0
|
6
7
|
|
7
8
|
if number.is_a?(String)
|
8
9
|
begin
|
@@ -15,10 +16,19 @@ module ExcelFunctions
|
|
15
16
|
return format if format.is_a?(Symbol)
|
16
17
|
|
17
18
|
case format
|
18
|
-
when
|
19
|
-
|
19
|
+
when /^(0(\.0*)?)%/
|
20
|
+
text(number*100, $1)+"%"
|
21
|
+
when /^(0+)$/
|
22
|
+
sprintf("%0#{$1.length}.0f", number)
|
23
|
+
when /#,#+(0\.0+)?/
|
24
|
+
formated_with_decimals = text(number, $1 || "0")
|
25
|
+
parts = formated_with_decimals.split('.')
|
26
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
|
27
|
+
parts.join('.')
|
28
|
+
when /0\.(0+)/
|
29
|
+
sprintf("%.#{$1.length}f", number)
|
20
30
|
else
|
21
|
-
format
|
31
|
+
raise ExcelToCodeException.new("in TEXT function format #{format} not yet supported by excel_to_code")
|
22
32
|
end
|
23
33
|
end
|
24
34
|
|
data/src/excel/formula_peg.rb
CHANGED
@@ -27,7 +27,7 @@ class Formula < RubyPeg
|
|
27
27
|
|
28
28
|
def function
|
29
29
|
node :function do
|
30
|
-
terminal(/[A-Z]+/) && ignore { terminal("(") } && space && optional { argument } && any_number_of { (space && ignore { terminal(",") } && space && argument) } && space && ignore { terminal(")") }
|
30
|
+
terminal(/(LOG10)|[A-Z]+/) && ignore { terminal("(") } && space && optional { argument } && any_number_of { (space && ignore { terminal(",") } && space && argument) } && space && ignore { terminal(")") }
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
data/src/excel/formula_peg.txt
CHANGED
@@ -2,7 +2,7 @@ formula := space? expression+
|
|
2
2
|
expression = string_join | comparison | arithmetic | thing
|
3
3
|
thing = percentage | function | array | brackets | any_reference | string | number | boolean | prefix | error | named_reference
|
4
4
|
argument = expression | null
|
5
|
-
function := /[A-Z]+/ `'(' space argument? (space `',' space argument)* space `')'
|
5
|
+
function := /(LOG10)|[A-Z]+/ `'(' space argument? (space `',' space argument)* space `')'
|
6
6
|
brackets := `'(' space expression+ space `')'
|
7
7
|
array := `'{' space row ( space `';' space row )* space `'}'
|
8
8
|
row := basic_type ( space `',' space basic_type )*
|
data/src/excel/table.rb
CHANGED
@@ -110,6 +110,10 @@ class Table
|
|
110
110
|
def all
|
111
111
|
ast_for_area @area.excel_start, @area.excel_finish
|
112
112
|
end
|
113
|
+
|
114
|
+
def data
|
115
|
+
ast_for_area @data_area.excel_start, @data_area.excel_finish
|
116
|
+
end
|
113
117
|
|
114
118
|
def ast_for_area(start,finish)
|
115
119
|
[:sheet_reference,@worksheet,[:area,start.to_sym,finish.to_sym]]
|
data/src/excel_to_code.rb
CHANGED
@@ -11,10 +11,17 @@ class InlineFormulaeAst
|
|
11
11
|
|
12
12
|
def map(ast)
|
13
13
|
return ast unless ast.is_a?(Array)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
case ast[0]
|
15
|
+
when :function
|
16
|
+
function(ast)
|
17
|
+
when :sheet_reference
|
18
|
+
sheet_reference(ast)
|
19
|
+
when :cell
|
20
|
+
cell(ast)
|
21
|
+
else
|
22
|
+
ast.each do |a|
|
23
|
+
map(a) if a.is_a?(Array)
|
24
|
+
end
|
18
25
|
end
|
19
26
|
ast
|
20
27
|
end
|
@@ -45,8 +52,8 @@ class InlineFormulaeAst
|
|
45
52
|
ref = ast[2][1].to_s.upcase.gsub('$','').to_sym
|
46
53
|
# FIXME: Need to check if valid worksheet and return [:error, "#REF!"] if not
|
47
54
|
# Now check user preference on this
|
48
|
-
ast_to_inline = ast_to_inline(sheet, ref)
|
49
55
|
return unless inline_ast.call(sheet,ref, references)
|
56
|
+
ast_to_inline = ast_or_blank(sheet, ref)
|
50
57
|
@count_replaced += 1
|
51
58
|
current_sheet_name.push(sheet)
|
52
59
|
map(ast_to_inline)
|
@@ -58,8 +65,8 @@ class InlineFormulaeAst
|
|
58
65
|
def cell(ast)
|
59
66
|
sheet = current_sheet_name.last
|
60
67
|
ref = ast[1].to_s.upcase.gsub('$', '').to_sym
|
61
|
-
ast_to_inline = ast_to_inline(sheet, ref)
|
62
68
|
if inline_ast.call(sheet, ref, references)
|
69
|
+
ast_to_inline = ast_or_blank(sheet, ref)
|
63
70
|
@count_replaced += 1
|
64
71
|
map(ast_to_inline)
|
65
72
|
ast.replace(ast_to_inline)
|
@@ -69,7 +76,7 @@ class InlineFormulaeAst
|
|
69
76
|
end
|
70
77
|
end
|
71
78
|
|
72
|
-
def
|
79
|
+
def ast_or_blank(sheet, ref)
|
73
80
|
ast_to_inline = references[[sheet, ref]]
|
74
81
|
return ast_to_inline if ast_to_inline
|
75
82
|
# Need to add a new blank cell and return ast for an inlined blank
|
@@ -140,6 +140,12 @@ class MapFormulaeToValues
|
|
140
140
|
ast.replace(formula_value( ast[1],*values))
|
141
141
|
end
|
142
142
|
|
143
|
+
def map_text(ast)
|
144
|
+
values = ast[2..-1].map { |a| value(a, nil) }
|
145
|
+
return if values.any? { |a| a == :not_a_value }
|
146
|
+
ast.replace(formula_value( ast[1],*values))
|
147
|
+
end
|
148
|
+
|
143
149
|
def map_if(ast)
|
144
150
|
condition_ast = ast[2]
|
145
151
|
true_option_ast = ast[3]
|
@@ -9,6 +9,8 @@ class ReplaceArithmeticOnRangesAst
|
|
9
9
|
next
|
10
10
|
when :sheet_reference, :table_reference, :local_table_reference
|
11
11
|
next
|
12
|
+
when :function
|
13
|
+
function(ast)
|
12
14
|
else
|
13
15
|
map(a)
|
14
16
|
end
|
@@ -18,6 +20,21 @@ class ReplaceArithmeticOnRangesAst
|
|
18
20
|
ast
|
19
21
|
end
|
20
22
|
|
23
|
+
# FIXME: Generalise this? Combine with Array formulae?
|
24
|
+
def function(ast)
|
25
|
+
unless [:RIGHT, :LEFT].include?(ast[1]) && ast[2][0] == :array
|
26
|
+
ast.each { |a| map(a) }
|
27
|
+
return
|
28
|
+
end
|
29
|
+
ast.replace(
|
30
|
+
array_map(ast[2]) do |cell|
|
31
|
+
a = ast.dup
|
32
|
+
a[2] = cell
|
33
|
+
a
|
34
|
+
end
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
21
38
|
# FIXME: DRY THIS UP
|
22
39
|
def comparison(ast)
|
23
40
|
left, operator, right = ast[1], ast[2], ast[3]
|
@@ -1,9 +1,10 @@
|
|
1
1
|
class NamedReferences
|
2
2
|
|
3
|
-
attr_accessor :named_references
|
3
|
+
attr_accessor :named_references, :table_data
|
4
4
|
|
5
|
-
def initialize(refs)
|
5
|
+
def initialize(refs, tables = {})
|
6
6
|
@named_references = refs
|
7
|
+
@table_data = tables
|
7
8
|
end
|
8
9
|
|
9
10
|
def reference_for(sheet,named_reference)
|
@@ -11,6 +12,7 @@ class NamedReferences
|
|
11
12
|
named_reference = named_reference.downcase.to_sym
|
12
13
|
@named_references[[sheet, named_reference]] ||
|
13
14
|
@named_references[named_reference] ||
|
15
|
+
@table_data[named_reference] ||
|
14
16
|
[:error, :"#NAME?"]
|
15
17
|
end
|
16
18
|
|
@@ -18,11 +20,11 @@ end
|
|
18
20
|
|
19
21
|
class ReplaceNamedReferencesAst
|
20
22
|
|
21
|
-
attr_accessor :named_references, :default_sheet_name
|
23
|
+
attr_accessor :named_references, :default_sheet_name, :table_data
|
22
24
|
|
23
|
-
def initialize(named_references, default_sheet_name = nil)
|
24
|
-
@named_references, @default_sheet_name = named_references, default_sheet_name
|
25
|
-
@named_references = NamedReferences.new(@named_references) unless @named_references.is_a?(NamedReferences)
|
25
|
+
def initialize(named_references, default_sheet_name = nil, table_data = {})
|
26
|
+
@named_references, @default_sheet_name, @table_data = named_references, default_sheet_name, table_data
|
27
|
+
@named_references = NamedReferences.new(@named_references, @table_data) unless @named_references.is_a?(NamedReferences)
|
26
28
|
end
|
27
29
|
|
28
30
|
def map(ast)
|
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.29
|
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-
|
11
|
+
date: 2014-07-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubypeg
|
@@ -122,7 +122,10 @@ description: "# excel_to_code\n\nConverts some excel spreadsheets (.xlsx, not .x
|
|
122
122
|
Doesn't implement references that involve range unions and lists (but does implement
|
123
123
|
standard ranges)\n5. Sometimes gives cells as being empty, when excel would give
|
124
124
|
the cell as having a numeric value of zero\n6. The generated C version does not
|
125
|
-
multithread and will give bad results if you try\n7. Newlines are removed from strings\
|
125
|
+
multithread and will give bad results if you try\n7. Newlines are removed from strings\n8.
|
126
|
+
The generated code uses floating point, rather than fully precise arithmetic, so
|
127
|
+
results can differ slightly\n9. The generated code uses the sprintf approach to
|
128
|
+
rounding (even-odd) rather than excel's 0.5 rounds away from zero.\n"
|
126
129
|
email: tamc@greenonblack.com
|
127
130
|
executables:
|
128
131
|
- excel_to_c
|
@@ -186,6 +189,7 @@ files:
|
|
186
189
|
- src/excel/excel_functions/index.rb
|
187
190
|
- src/excel/excel_functions/int.rb
|
188
191
|
- src/excel/excel_functions/isblank.rb
|
192
|
+
- src/excel/excel_functions/iserr.rb
|
189
193
|
- src/excel/excel_functions/isnumber.rb
|
190
194
|
- src/excel/excel_functions/large.rb
|
191
195
|
- src/excel/excel_functions/left.rb
|