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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f1d440b53cf5789e80137607f6d172546f719b7
4
- data.tar.gz: 49eb81b9dce39eab5c56afe5cbc8a251d1539994
3
+ metadata.gz: fa747184660d6eb2e3a92bce25849e0683eff073
4
+ data.tar.gz: a959fd27d7000dc9167a4e63972a544fef83e0a8
5
5
  SHA512:
6
- metadata.gz: 58d2670adbdf448425a435297478cfdfb8f4350d2d68a8e85fdcf7a4b8a778b8aa9aab856b4693b54325707950fac0f7bb39600e5b180f1b95ea43032d5056c4
7
- data.tar.gz: edc5688112befeb6935b394a8169bdf683ab5071dff8423cd615336539144f55f7b3f488d6ede08eba68fbc2c501a64decb37fe98740f440c1e140f0ac422aaf
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.
@@ -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 ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),language)
223
- self.xml_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'xml')
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 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
- references_to_keep = @cells_that_can_be_set_at_runtime[sheet]
930
- if references_to_keep && (references_to_keep == :all || references_to_keep.include?(cell))
940
+ ref = [sheet,cell]
941
+ if must_keep?(ref)
931
942
  false
932
943
  else
933
- ast = references[[sheet,cell]]
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, "%d%%",(int) round(number_v.number*100));
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 format_v;
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
@@ -0,0 +1,9 @@
1
+ module ExcelFunctions
2
+
3
+ def iserr(a)
4
+ return false if a == :na
5
+ return true if a.is_a?(Symbol)
6
+ false
7
+ end
8
+
9
+ 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 '0%'
19
- "#{(number * 100).round}%"
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
 
@@ -111,3 +111,5 @@ require_relative 'excel_functions/ensure_is_number'
111
111
  require_relative 'excel_functions/value'
112
112
 
113
113
  require_relative 'excel_functions/ln'
114
+
115
+ require_relative 'excel_functions/iserr'
@@ -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
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  class ExcelToCode
2
- def self.version() "0.2.28" end
2
+ def self.version() "0.2.29" end
3
3
  end
4
4
 
5
5
  require_relative 'commands'
@@ -11,10 +11,17 @@ class InlineFormulaeAst
11
11
 
12
12
  def map(ast)
13
13
  return ast unless ast.is_a?(Array)
14
- if respond_to?(ast[0])
15
- send(ast[0], ast)
16
- else # In this case needs to be an else because don't want to map first argument in OFFSET(cell_to_offset_from_shouldn't_be_mapped, rows, columns)
17
- ast.each { |a| map(a) }
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 ast_to_inline(sheet, ref)
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.28
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-06-16 00:00:00.000000000 Z
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\n"
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