excel_to_code 0.2.28 → 0.2.29
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/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
|