excel_to_code 0.1.8 → 0.1.10

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/TODO +0 -1
  3. data/src/commands/excel_to_c.rb +6 -5
  4. data/src/commands/excel_to_ruby.rb +2 -1
  5. data/src/commands/excel_to_x.rb +69 -37
  6. data/src/compile/c/a.out +0 -0
  7. data/src/compile/c/compile_named_reference_setters.rb +1 -1
  8. data/src/compile/c/compile_to_c.rb +1 -1
  9. data/src/compile/c/compile_to_c_header.rb +1 -1
  10. data/src/compile/c/excel_to_c_runtime.c +117 -1
  11. data/src/compile/c/map_formulae_to_c.rb +4 -0
  12. data/src/compile/c/map_sheet_names_to_c_names.rb +1 -1
  13. data/src/compile/c/map_values_to_c.rb +2 -1
  14. data/src/compile/ruby/compile_to_ruby.rb +2 -2
  15. data/src/compile/ruby/compile_to_ruby_unit_test.rb +1 -1
  16. data/src/compile/ruby/map_formulae_to_ruby.rb +5 -0
  17. data/src/compile/ruby/map_values_to_ruby.rb +2 -1
  18. data/src/excel/excel_functions.rb +10 -0
  19. data/src/excel/excel_functions/cell.rb +14 -0
  20. data/src/excel/excel_functions/mid.rb +20 -0
  21. data/src/excel/excel_functions/negative.rb +2 -0
  22. data/src/excel/excel_functions/pv.rb +37 -0
  23. data/src/excel/excel_functions/text.rb +25 -0
  24. data/src/excel/excel_functions/trim.rb +8 -0
  25. data/src/excel/table.rb +1 -0
  26. data/src/extract/check_for_unknown_functions.rb +1 -1
  27. data/src/rewrite/ast_expand_array_formulae.rb +4 -6
  28. data/src/rewrite/rewrite_array_formulae.rb +2 -2
  29. data/src/rewrite/rewrite_array_formulae_to_arrays.rb +1 -1
  30. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +1 -1
  31. data/src/rewrite/rewrite_formulae_to_ast.rb +1 -1
  32. data/src/rewrite/rewrite_merge_formulae_and_values.rb +2 -2
  33. data/src/rewrite/rewrite_named_reference_names.rb +1 -1
  34. data/src/rewrite/rewrite_relationship_id_to_filename.rb +1 -1
  35. data/src/rewrite/rewrite_shared_formulae.rb +2 -2
  36. data/src/rewrite/rewrite_values_to_ast.rb +1 -1
  37. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +2 -2
  38. data/src/rewrite/rewrite_worksheet_names.rb +6 -3
  39. data/src/simplify.rb +2 -0
  40. data/src/simplify/inline_formulae.rb +18 -1
  41. data/src/simplify/map_formulae_to_values.rb +18 -6
  42. data/src/simplify/remove_cells.rb +1 -1
  43. data/src/simplify/replace_arrays_with_single_cells.rb +1 -1
  44. data/src/simplify/replace_column_with_column_number.rb +58 -0
  45. data/src/simplify/replace_common_elements_in_formulae.rb +1 -1
  46. data/src/simplify/replace_formulae_with_calculated_values.rb +7 -1
  47. data/src/simplify/replace_indirects_with_references.rb +16 -3
  48. data/src/simplify/replace_named_references.rb +1 -1
  49. data/src/simplify/replace_offsets_with_references.rb +66 -0
  50. data/src/simplify/replace_ranges_with_array_literals.rb +1 -1
  51. data/src/simplify/replace_shared_strings.rb +1 -1
  52. data/src/simplify/replace_table_references.rb +2 -2
  53. data/src/simplify/replace_values_with_constants.rb +1 -1
  54. data/src/simplify/simplify_arithmetic.rb +1 -1
  55. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 880640b9610ce6658c3ba7a5ff8d6f96048d0705
4
- data.tar.gz: 08f680f1229c0e00d769b3b7024e452a32a5ebd5
3
+ metadata.gz: 4e983a098e0ba3377ae5b0d42b53c5d627e765e8
4
+ data.tar.gz: dd9b3766572dd84f5bf980fb256bfd7ce70fdb49
5
5
  SHA512:
6
- metadata.gz: 244303086ecf46f8be290f2d1c46f4c2b929c21b2bca0f7129d9edeb65e06e068824039250f650d12b3eaa4f85b2e9d08423b042cbf71b68e9a4d9f4c3311e4f
7
- data.tar.gz: d40bf490120ec061bf94fbf65c32daf136edd1d9abefb833c1e1cf8a0c17940d80c6fe5e8ca8fd436c22aad17db8f3cbdc5312a55338317ab16614b7be107c98
6
+ metadata.gz: b829c3fb99bd61216b7df001ad320a9a0ecd6f6157fc07ac0c368417354e853544f25ae8b31b43dde6fb788480c6ac9e8559b6646fd8a5d55bd4a054885c0e62
7
+ data.tar.gz: 6d1f1519d8daf601c6028c6c116eadd8c76dada8edb5153ee42d49946fa97831b18d0d086e46a59b49823c6126e893638f0247c2e30b2de08a3f053a917bc49a
data/TODO CHANGED
@@ -19,7 +19,6 @@ See doc/How_to_add_a_missing_function.md
19
19
 
20
20
  * Optimize IF, CHOOSE, MATCH, VLOOKUP and similar functions so that they don't have to calculate all their arguments
21
21
  * Fix it so that cells that are being reported as empty, that excel would give a numeric value of zero, are fixed
22
- * Fix so that detects when it has finished replacing cells with values, rather than just doing a fixed number of cycles
23
22
 
24
23
  ## Things that are badly written
25
24
 
@@ -36,13 +36,14 @@ class ExcelToC < ExcelToX
36
36
 
37
37
  # Now we have to put all the initial definitions out
38
38
  o.puts "// definitions"
39
+ o.puts "static ExcelValue ORIGINAL_EXCEL_FILENAME = {.type = ExcelString, .string = #{excel_file.inspect} };"
39
40
 
40
41
  i = input("Common elements")
41
42
  c = CompileToCHeader.new
42
43
  c.gettable = lambda { |ref| false }
43
44
  c.rewrite(i,w,o)
44
45
  i.rewind
45
- number_of_refs += i.lines.to_a.size
46
+ number_of_refs += i.each_line.to_a.size
46
47
  close(i)
47
48
 
48
49
  worksheets do |name,xml_filename|
@@ -54,7 +55,7 @@ class ExcelToC < ExcelToX
54
55
  i = input([name,"Formulae"])
55
56
  c.rewrite(i,w,o)
56
57
  i.rewind
57
- number_of_refs += i.lines.to_a.size
58
+ number_of_refs += i.each_line.to_a.size
58
59
  close(i)
59
60
  end
60
61
 
@@ -82,7 +83,7 @@ class ExcelToC < ExcelToX
82
83
  o.puts "// starting the value constants"
83
84
  mapper = MapValuesToCStructs.new
84
85
  i = input("Constants")
85
- i.lines do |line|
86
+ i.each_line do |line|
86
87
  begin
87
88
  ref, formula = line.split("\t")
88
89
  ast = eval(formula)
@@ -344,7 +345,7 @@ END
344
345
 
345
346
  # Getters
346
347
  i = input('Named references to keep')
347
- i.lines.each do |line|
348
+ i.each_line do |line|
348
349
  name = line.strip.split("\t").first
349
350
  o.puts " attach_function '#{name}', [], ExcelValue.by_value"
350
351
  end
@@ -352,7 +353,7 @@ END
352
353
 
353
354
  # Setters
354
355
  i = input('Named references to set')
355
- i.lines.each do |line|
356
+ i.each_line do |line|
356
357
  name = line.strip.split("\t").first
357
358
  o.puts " attach_function 'set_#{name}', [ExcelValue.by_value], :void"
358
359
  end
@@ -30,6 +30,7 @@ class ExcelToRuby < ExcelToX
30
30
  o.puts ""
31
31
  o.puts "class #{ruby_module_name}"
32
32
  o.puts " include ExcelFunctions"
33
+ o.puts " def original_excel_filename; #{excel_file.inspect}; end"
33
34
 
34
35
  o.puts
35
36
  o.puts " # Starting common elements"
@@ -66,7 +67,7 @@ class ExcelToRuby < ExcelToX
66
67
  o.puts " # starting initializer"
67
68
  o.puts " def initialize"
68
69
  d = input('Defaults')
69
- d.lines do |line|
70
+ d.each_line do |line|
70
71
  o.puts line
71
72
  end
72
73
  o.puts " end"
@@ -110,9 +110,11 @@ class ExcelToX
110
110
  self.cells_that_can_be_set_at_runtime ||= {}
111
111
 
112
112
  # Make sure that all the cell names are downcase and don't have any $ in them
113
- cells_that_can_be_set_at_runtime.keys.each do |sheet|
114
- next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
115
- cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').upcase }
113
+ if cells_that_can_be_set_at_runtime.is_a?(Hash)
114
+ cells_that_can_be_set_at_runtime.keys.each do |sheet|
115
+ next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
116
+ cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').upcase }
117
+ end
116
118
  end
117
119
 
118
120
  # Make sure that all the cell names are downcase and don't have any $ in them
@@ -168,9 +170,9 @@ class ExcelToX
168
170
  simplify_worksheets # Replacing shared strings and named references with their actual values, tidying arithmetic
169
171
 
170
172
  # In case this hasn't been set by the user
171
- if cells_that_can_be_set_at_runtime.empty?
173
+ if @cells_that_can_be_set_at_runtime.empty?
172
174
  log.info "Creating a good set of cells that should be settable"
173
- create_a_good_set_of_cells_that_should_be_settable_at_runtime
175
+ @cells_that_can_be_set_at_runtime = a_good_set_of_cells_that_should_be_settable_at_runtime
174
176
  end
175
177
 
176
178
  if named_references_that_can_be_set_at_runtime == :where_possible
@@ -313,7 +315,7 @@ class ExcelToX
313
315
  table_filenames = input(name, "Worksheet tables")
314
316
  tables = intermediate(name, "Worksheet tables")
315
317
  table_extractor = ExtractTable.new(name)
316
- table_filenames.lines.each do |line|
318
+ table_filenames.each_line do |line|
317
319
  table_xml = xml(File.join('worksheets',line.strip))
318
320
  table_extractor.extract(table_xml, tables)
319
321
  end
@@ -328,7 +330,7 @@ class ExcelToX
328
330
  worksheets do |name,xml_filename|
329
331
  log.info "Merging table files for #{name}"
330
332
  worksheet_table_file = input([name, "Worksheet tables"])
331
- worksheet_table_file.lines do |line|
333
+ worksheet_table_file.each_line do |line|
332
334
  merged_table_file.puts line
333
335
  end
334
336
  close worksheet_table_file
@@ -477,6 +479,9 @@ class ExcelToX
477
479
  def work_out_which_named_references_can_be_set_at_runtime
478
480
  return unless @named_references_that_can_be_set_at_runtime
479
481
  return unless @named_references_that_can_be_set_at_runtime == :where_possible
482
+ cells_that_can_be_set = @cells_that_can_be_set_at_runtime
483
+ cells_that_can_be_set = a_good_set_of_cells_that_should_be_settable_at_runtime if cells_that_can_be_set == :named_references_only
484
+ cells_that_can_be_set_due_to_named_reference = Hash.new { |h,k| h[k] = Array.new }
480
485
  @named_references_that_can_be_set_at_runtime = []
481
486
  all_named_references = named_references
482
487
  @named_references_to_keep.each do |name|
@@ -484,23 +489,34 @@ class ExcelToX
484
489
  if ref.first == :sheet_reference
485
490
  sheet = ref[1]
486
491
  cell = ref[2][1].gsub('$','')
487
- s = @cells_that_can_be_set_at_runtime[sheet]
488
- @named_references_that_can_be_set_at_runtime << name if s && s.include?(cell)
492
+ s = cells_that_can_be_set[sheet]
493
+ if s && s.include?(cell)
494
+ @named_references_that_can_be_set_at_runtime << name
495
+ cells_that_can_be_set_due_to_named_reference[sheet] << cell
496
+ cells_that_can_be_set_due_to_named_reference[sheet].uniq!
497
+ end
489
498
  elsif ref.first.is_a?(Array)
490
499
  ref = ref.first
491
- p ref
492
- settable = ref.all? do |row|
493
- ref.all? do |column|
494
- p column
495
- sheet = column[1]
496
- cell = column[2][1].gsub('$','')
497
- s = @cells_that_can_be_set_at_runtime[sheet]
498
- s && s.include?(cell)
500
+ settable = ref.all? do |r|
501
+ sheet = r[1]
502
+ cell = r[2][1].gsub('$','')
503
+ s = cells_that_can_be_set[sheet]
504
+ s && s.include?(cell)
505
+ end
506
+ if settable
507
+ @named_references_that_can_be_set_at_runtime << name
508
+ ref.each do |r|
509
+ sheet = r[1]
510
+ cell = r[2][1].gsub('$','')
511
+ cells_that_can_be_set_due_to_named_reference[sheet] << cell
512
+ cells_that_can_be_set_due_to_named_reference[sheet].uniq!
499
513
  end
500
514
  end
501
- @named_references_that_can_be_set_at_runtime << name if settable
502
515
  end
503
516
  end
517
+ if @cells_that_can_be_set_at_runtime == :named_references_only
518
+ @cells_that_can_be_set_at_runtime = cells_that_can_be_set_due_to_named_reference
519
+ end
504
520
  end
505
521
 
506
522
  # FIXME: Feels like a kludge
@@ -510,7 +526,7 @@ class ExcelToX
510
526
 
511
527
  i = input('Named references')
512
528
  o = intermediate('Named references to keep')
513
- i.lines.each do |line|
529
+ i.each_line do |line|
514
530
  sheet, name, ref = *line.split("\t")
515
531
  key = sheet.length != 0 ? [sheet, name] : name
516
532
  o.puts line if named_references_to_keep.include?(key) || named_references_that_can_be_set_at_runtime.include?(key)
@@ -519,7 +535,7 @@ class ExcelToX
519
535
 
520
536
  i.rewind
521
537
  o = intermediate('Named references to set')
522
- i.lines.each do |line|
538
+ i.each_line do |line|
523
539
  sheet, name, ref = *line.split("\t")
524
540
  key = sheet.length != 0 ? [sheet, name] : name
525
541
  o.puts line if named_references_that_can_be_set_at_runtime.include?(key)
@@ -550,25 +566,35 @@ class ExcelToX
550
566
  end
551
567
  end
552
568
 
553
- # FIXME: This should work out how often it needs to operate, rather than having a hardwired 4
554
569
  def replace_formulae_with_their_results
555
- 4.times do
556
- replace_indirects
570
+ number_of_passes = 0
571
+ begin
572
+ number_of_passes += 1
573
+ @replacements_made_in_the_last_pass = 0
574
+ replace_indirects_and_offsets
557
575
  replace_formulae_with_calculated_values
558
576
  replace_references_to_values_with_values
559
- end
577
+ log.info "Pass #{number_of_passes}: Made #{@replacements_made_in_the_last_pass} replacements"
578
+ if number_of_passes > 20
579
+ log.warn "Made more than 20 passes, so aborting"
580
+ break
581
+ end
582
+ end while @replacements_made_in_the_last_pass > 0
560
583
  end
561
584
 
562
- # There is no support for INDIRECT in the ruby or c runtime
585
+ # There is no support for INDIRECT or OFFSET in the ruby or c runtime
563
586
  # However, in many cases it isn't needed, because we can work
564
- # out the value of the indirect at compile time and eliminate it
565
- def replace_indirects
587
+ # out the value of the indirect or OFFSET at compile time and eliminate it
588
+ def replace_indirects_and_offsets
566
589
  worksheets do |name,xml_filename|
567
- log.info "Replacing indirects in #{name}"
590
+ log.info "Replacing INDIRECT, OFFSET and COLUMN functions in #{name}"
568
591
 
569
592
  # First of all we replace any indirects where their values can be calculated at compile time with those
570
- # calculated values (e.g., INDIRECT("A"&1) can be turned into A1)
571
- replace ReplaceIndirectsWithReferences, [name, 'Formulae'], [name, 'Formulae']
593
+ # calculated values (e.g., INDIRECT("A"&1) can be turned into A1 and OFFSET(A1,1,1,2,2) can be turned into B2:C3)
594
+ [ReplaceIndirectsWithReferences.new, ReplaceOffsetsWithReferences.new, ReplaceColumnWithColumnNumber.new].each do |r|
595
+ replace r, [name, 'Formulae'], [name, 'Formulae']
596
+ @replacements_made_in_the_last_pass += r.replacements_made_in_the_last_pass
597
+ end
572
598
 
573
599
  # The result of the indirect might be a named reference, which we need to simplify
574
600
  r = ReplaceNamedReferences.new
@@ -589,7 +615,10 @@ class ExcelToX
589
615
  # If a formula's value can be calculated at compile time, it is replaced with its calculated value (e.g., 1+1 gets replaced with 2)
590
616
  def replace_formulae_with_calculated_values
591
617
  worksheets do |name,xml_filename|
592
- replace ReplaceFormulaeWithCalculatedValues, [name, 'Formulae'], [name, 'Formulae']
618
+ r = ReplaceFormulaeWithCalculatedValues.new
619
+ r.excel_file = excel_file
620
+ replace r, [name, 'Formulae'], [name, 'Formulae']
621
+ @replacements_made_in_the_last_pass += r.replacements_made_in_the_last_pass
593
622
  end
594
623
  end
595
624
 
@@ -623,6 +652,7 @@ class ExcelToX
623
652
  r.default_sheet_name = name
624
653
  replace r, [name, 'Formulae'], [name, 'Formulae']
625
654
  end
655
+ @replacements_made_in_the_last_pass += r.replacements_made_in_the_last_pass
626
656
  end
627
657
 
628
658
  # If 'cells to keep' are specified, then other cells are removed, unless
@@ -773,10 +803,11 @@ class ExcelToX
773
803
  # or in cells_that_can_be_set_at_runtime, then we assume that
774
804
  # all value cells should be settable if they are referenced by
775
805
  # any other forumla.
776
- def create_a_good_set_of_cells_that_should_be_settable_at_runtime
806
+ def a_good_set_of_cells_that_should_be_settable_at_runtime
777
807
  references = all_formulae
778
808
  counter = CountFormulaReferences.new
779
809
  count = counter.count(references)
810
+ settable_cells = {}
780
811
 
781
812
  count.each do |sheet,keys|
782
813
  keys.each do |ref,count|
@@ -784,11 +815,12 @@ class ExcelToX
784
815
  ast = references[sheet][ref]
785
816
  next unless ast
786
817
  if [:blank,:number,:null,:string,:shared_string,:constant,:percentage,:error,:boolean_true,:boolean_false].include?(ast.first)
787
- @cells_that_can_be_set_at_runtime[sheet] ||= []
788
- @cells_that_can_be_set_at_runtime[sheet] << ref.upcase
818
+ settable_cells[sheet] ||= []
819
+ settable_cells[sheet] << ref.upcase
789
820
  end
790
821
  end
791
- end
822
+ end
823
+ return settable_cells
792
824
  end
793
825
 
794
826
  # UTILITY FUNCTIONS
@@ -820,7 +852,7 @@ class ExcelToX
820
852
  worksheets do |name,xml_filename|
821
853
  r = references[name] = {}
822
854
  i = input([name,'Formulae'])
823
- i.lines do |line|
855
+ i.each_line do |line|
824
856
  line =~ /^(.*?)\t(.*)$/
825
857
  ref, ast = $1, $2
826
858
  r[ref] = eval(ast)
@@ -841,7 +873,7 @@ class ExcelToX
841
873
  def worksheets(&block)
842
874
  unless @worksheet_filenames
843
875
  worksheet_names = input('Worksheet names')
844
- @worksheet_filenames = worksheet_names.lines.map do |line|
876
+ @worksheet_filenames = worksheet_names.each_line.map do |line|
845
877
  name, filename = *line.split("\t")
846
878
  [name, filename.strip]
847
879
  end
data/src/compile/c/a.out CHANGED
Binary file
@@ -74,7 +74,7 @@ class CompileNamedReferenceSetters
74
74
  mapper.sheet_names = Hash[sheet_names.readlines.map { |line| line.strip.split("\t")}]
75
75
  mapper.cells_that_can_be_set_at_runtime = cells_that_can_be_set_at_runtime
76
76
 
77
- named_references.lines do |line|
77
+ named_references.each_line do |line|
78
78
  name, reference = line.split("\t")
79
79
  ast = eval(reference)
80
80
  output.puts "void set_#{name}(ExcelValue newValue) {"
@@ -19,7 +19,7 @@ class CompileToC
19
19
  mapper.worksheet = worksheet
20
20
  mapper.sheet_names = Hash[sheet_names_file.readlines.map { |line| line.strip.split("\t")}]
21
21
  c_name = mapper.sheet_names[worksheet] || worksheet
22
- input.lines do |line|
22
+ input.each_line do |line|
23
23
  begin
24
24
  ref, formula = line.split("\t")
25
25
  ast = eval(formula)
@@ -12,7 +12,7 @@ class CompileToCHeader
12
12
  c_name = Hash[sheet_names_file.readlines.map { |line| line.strip.split("\t")}][worksheet]
13
13
  self.gettable ||= lambda { |ref| true }
14
14
  self.settable ||= lambda { |ref| false }
15
- input.lines do |line|
15
+ input.each_line do |line|
16
16
  begin
17
17
  ref, formula = line.split("\t")
18
18
  static_or_not = (gettable.call(ref) || settable.call(ref)) ? "" : "static "
@@ -67,6 +67,9 @@ static ExcelValue mod(ExcelValue a_v, ExcelValue b_v);
67
67
  static ExcelValue negative(ExcelValue a_v);
68
68
  static ExcelValue pmt(ExcelValue rate_v, ExcelValue number_of_periods_v, ExcelValue present_value_v);
69
69
  static ExcelValue power(ExcelValue a_v, ExcelValue b_v);
70
+ static ExcelValue pv_3(ExcelValue a_v, ExcelValue b_v, ExcelValue c_v);
71
+ static ExcelValue pv_4(ExcelValue a_v, ExcelValue b_v, ExcelValue c_v, ExcelValue d_v);
72
+ static ExcelValue pv_5(ExcelValue a_v, ExcelValue b_v, ExcelValue c_v, ExcelValue d_v, ExcelValue e_v);
70
73
  static ExcelValue excel_round(ExcelValue number_v, ExcelValue decimal_places_v);
71
74
  static ExcelValue rounddown(ExcelValue number_v, ExcelValue decimal_places_v);
72
75
  static ExcelValue roundup(ExcelValue number_v, ExcelValue decimal_places_v);
@@ -77,6 +80,7 @@ static ExcelValue sumifs(ExcelValue sum_range_v, int number_of_arguments, ExcelV
77
80
  static ExcelValue sumif(ExcelValue check_range_v, ExcelValue criteria_v, ExcelValue sum_range_v );
78
81
  static ExcelValue sumif_2(ExcelValue check_range_v, ExcelValue criteria_v);
79
82
  static ExcelValue sumproduct(int number_of_arguments, ExcelValue *arguments);
83
+ static ExcelValue text(ExcelValue number_v, ExcelValue format_v);
80
84
  static ExcelValue vlookup_3(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue column_number_v);
81
85
  static ExcelValue vlookup(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue column_number_v, ExcelValue match_type_v);
82
86
 
@@ -122,7 +126,7 @@ static ExcelValue new_excel_number(double number) {
122
126
  static ExcelValue new_excel_string(char *string) {
123
127
  cell_counter++;
124
128
  HEAPCHECK
125
- ExcelValue new_cell = cells[cell_counter];
129
+ ExcelValue new_cell = cells[cell_counter];
126
130
  new_cell.type = ExcelString;
127
131
  new_cell.string = string;
128
132
  return new_cell;
@@ -1071,6 +1075,57 @@ static ExcelValue pmt(ExcelValue rate_v, ExcelValue number_of_periods_v, ExcelVa
1071
1075
  return new_excel_number(-present_value*(rate*(pow((1+rate),number_of_periods)))/((pow((1+rate),number_of_periods))-1));
1072
1076
  }
1073
1077
 
1078
+ static ExcelValue pv_3(ExcelValue rate_v, ExcelValue nper_v, ExcelValue pmt_v) {
1079
+ return pv_4(rate_v, nper_v, pmt_v, ZERO);
1080
+ }
1081
+
1082
+ static ExcelValue pv_4(ExcelValue rate_v, ExcelValue nper_v, ExcelValue pmt_v, ExcelValue fv_v) {
1083
+ return pv_5(rate_v, nper_v, pmt_v, fv_v, ZERO);
1084
+ }
1085
+
1086
+ static ExcelValue pv_5(ExcelValue rate_v, ExcelValue nper_v, ExcelValue pmt_v, ExcelValue fv_v, ExcelValue type_v ) {
1087
+ CHECK_FOR_PASSED_ERROR(rate_v)
1088
+ CHECK_FOR_PASSED_ERROR(nper_v)
1089
+ CHECK_FOR_PASSED_ERROR(pmt_v)
1090
+ CHECK_FOR_PASSED_ERROR(fv_v)
1091
+ CHECK_FOR_PASSED_ERROR(type_v)
1092
+
1093
+ NUMBER(rate_v, rate)
1094
+ NUMBER(nper_v, nper)
1095
+ NUMBER(pmt_v, payment)
1096
+ NUMBER(fv_v, fv)
1097
+ NUMBER(type_v, start_of_period)
1098
+ CHECK_FOR_CONVERSION_ERROR
1099
+
1100
+ if(rate< 0) {
1101
+ return VALUE;
1102
+ }
1103
+
1104
+ double present_value = 0;
1105
+
1106
+ // Sum up the payments
1107
+ if(rate == 0) {
1108
+ present_value = -payment * nper;
1109
+ } else {
1110
+ present_value = -payment * ((1-pow(1+rate,-nper))/rate);
1111
+ }
1112
+
1113
+ // Adjust for beginning or end of period
1114
+ if(start_of_period == 0) {
1115
+ // Do Nothing
1116
+ } else if(start_of_period == 1) {
1117
+ present_value = present_value * (1+rate);
1118
+ } else {
1119
+ return VALUE;
1120
+ }
1121
+
1122
+ // Add on the final value
1123
+ present_value = present_value - (fv/pow(1+rate,nper));
1124
+
1125
+ return new_excel_number(present_value);
1126
+ }
1127
+
1128
+
1074
1129
  static ExcelValue power(ExcelValue a_v, ExcelValue b_v) {
1075
1130
  CHECK_FOR_PASSED_ERROR(a_v)
1076
1131
  CHECK_FOR_PASSED_ERROR(b_v)
@@ -1517,6 +1572,56 @@ static ExcelValue sumproduct(int number_of_arguments, ExcelValue *arguments) {
1517
1572
  return new_excel_number(sum);
1518
1573
  }
1519
1574
 
1575
+ static ExcelValue text(ExcelValue number_v, ExcelValue format_v) {
1576
+ CHECK_FOR_PASSED_ERROR(number_v)
1577
+ CHECK_FOR_PASSED_ERROR(format_v)
1578
+
1579
+ char *s;
1580
+ char *p;
1581
+ double n;
1582
+ ExcelValue result;
1583
+
1584
+ if(number_v.type == ExcelEmpty) {
1585
+ number_v = ZERO;
1586
+ }
1587
+
1588
+ if(format_v.type == ExcelEmpty) {
1589
+ return new_excel_string("");
1590
+ }
1591
+
1592
+ if(number_v.type == ExcelString) {
1593
+ s = number_v.string;
1594
+ if (s == NULL || *s == '\0' || isspace(*s)) {
1595
+ number_v = ZERO;
1596
+ }
1597
+ n = strtod (s, &p);
1598
+ if(*p == '\0') {
1599
+ number_v = new_excel_number(n);
1600
+ }
1601
+ }
1602
+
1603
+ if(number_v.type != ExcelNumber) {
1604
+ return number_v;
1605
+ }
1606
+
1607
+ if(format_v.type != ExcelString) {
1608
+ return format_v;
1609
+ }
1610
+
1611
+ if(format_v.string == "0%") {
1612
+ // FIXME: Too little?
1613
+ s = malloc(100);
1614
+ free_later(s);
1615
+ sprintf(s, "%d%%",(int) round(number_v.number*100));
1616
+ result = new_excel_string(s);
1617
+ } else {
1618
+ return format_v;
1619
+ }
1620
+
1621
+ // inspect_excel_value(result);
1622
+ return result;
1623
+ }
1624
+
1520
1625
  static ExcelValue vlookup_3(ExcelValue lookup_value_v,ExcelValue lookup_table_v, ExcelValue column_number_v) {
1521
1626
  return vlookup(lookup_value_v,lookup_table_v,column_number_v,TRUE);
1522
1627
  }
@@ -2165,7 +2270,18 @@ int test_functions() {
2165
2270
  ExcelValue sum_array_0_v = new_excel_range(sum_array_0,3,1);
2166
2271
  ExcelValue sum_array_1[] = {sum_array_0_v};
2167
2272
  assert(sum(1,sum_array_1).number == 1253.8718091935484);
2273
+
2274
+ // Test PV
2275
+ assert((int) pv_3(new_excel_number(0.03), new_excel_number(12), new_excel_number(100)).number == -995);
2276
+ assert((int) pv_4(new_excel_number(0.03), new_excel_number(12), new_excel_number(-100), new_excel_number(100)).number == 925);
2277
+ assert((int) pv_5(new_excel_number(0.03), new_excel_number(12), new_excel_number(-100), new_excel_number(-100), new_excel_number(1)).number == 1095);
2168
2278
 
2279
+ // Test TEXT
2280
+ assert(strcmp(text(new_excel_number(1.0), new_excel_string("0%")).string, "100%") == 0);
2281
+ assert(strcmp(text(new_excel_string("1"), new_excel_string("0%")).string, "100%") == 0);
2282
+ assert(strcmp(text(BLANK, new_excel_string("0%")).string, "0%") == 0);
2283
+ assert(strcmp(text(new_excel_number(1.0), BLANK).string, "") == 0);
2284
+ assert(strcmp(text(new_excel_string("ASGASD"), new_excel_string("0%")).string, "ASGASD") == 0);
2169
2285
  // Release memory
2170
2286
  free_all_allocated_memory();
2171
2287