excel_to_code 0.0.1

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 (123) hide show
  1. data/README +41 -0
  2. data/bin/excel_to_c +63 -0
  3. data/bin/excel_to_ruby +9 -0
  4. data/src/commands.rb +2 -0
  5. data/src/commands/excel_to_c.rb +858 -0
  6. data/src/commands/excel_to_ruby.rb +620 -0
  7. data/src/compile.rb +2 -0
  8. data/src/compile/c.rb +5 -0
  9. data/src/compile/c/compile_to_c.rb +62 -0
  10. data/src/compile/c/compile_to_c_header.rb +26 -0
  11. data/src/compile/c/compile_to_c_unit_test.rb +42 -0
  12. data/src/compile/c/excel_to_c_runtime.c +2029 -0
  13. data/src/compile/c/map_formulae_to_c.rb +184 -0
  14. data/src/compile/c/map_sheet_names_to_c_names.rb +19 -0
  15. data/src/compile/c/map_values_to_c.rb +85 -0
  16. data/src/compile/c/map_values_to_c_structs.rb +37 -0
  17. data/src/compile/ruby.rb +3 -0
  18. data/src/compile/ruby/compile_to_ruby.rb +33 -0
  19. data/src/compile/ruby/compile_to_ruby_unit_test.rb +28 -0
  20. data/src/compile/ruby/excel_to_ruby_runtime.rb +1 -0
  21. data/src/compile/ruby/map_formulae_to_ruby.rb +95 -0
  22. data/src/compile/ruby/map_sheet_names_to_ruby_names.rb +19 -0
  23. data/src/compile/ruby/map_values_to_ruby.rb +65 -0
  24. data/src/excel.rb +5 -0
  25. data/src/excel/area.rb +93 -0
  26. data/src/excel/excel_functions.rb +84 -0
  27. data/src/excel/excel_functions/abs.rb +14 -0
  28. data/src/excel/excel_functions/add.rb +18 -0
  29. data/src/excel/excel_functions/and.rb +30 -0
  30. data/src/excel/excel_functions/apply_to_range.rb +17 -0
  31. data/src/excel/excel_functions/average.rb +12 -0
  32. data/src/excel/excel_functions/choose.rb +18 -0
  33. data/src/excel/excel_functions/cosh.rb +9 -0
  34. data/src/excel/excel_functions/count.rb +9 -0
  35. data/src/excel/excel_functions/counta.rb +8 -0
  36. data/src/excel/excel_functions/divide.rb +23 -0
  37. data/src/excel/excel_functions/excel_equal.rb +20 -0
  38. data/src/excel/excel_functions/excel_if.rb +8 -0
  39. data/src/excel/excel_functions/excel_match.rb +51 -0
  40. data/src/excel/excel_functions/find.rb +39 -0
  41. data/src/excel/excel_functions/iferror.rb +10 -0
  42. data/src/excel/excel_functions/index.rb +48 -0
  43. data/src/excel/excel_functions/left.rb +12 -0
  44. data/src/excel/excel_functions/less_than.rb +26 -0
  45. data/src/excel/excel_functions/less_than_or_equal.rb +26 -0
  46. data/src/excel/excel_functions/max.rb +12 -0
  47. data/src/excel/excel_functions/min.rb +12 -0
  48. data/src/excel/excel_functions/mod.rb +15 -0
  49. data/src/excel/excel_functions/more_than.rb +26 -0
  50. data/src/excel/excel_functions/more_than_or_equal.rb +26 -0
  51. data/src/excel/excel_functions/multiply.rb +24 -0
  52. data/src/excel/excel_functions/negative.rb +12 -0
  53. data/src/excel/excel_functions/not_equal.rb +19 -0
  54. data/src/excel/excel_functions/number_argument.rb +30 -0
  55. data/src/excel/excel_functions/pi.rb +7 -0
  56. data/src/excel/excel_functions/pmt.rb +16 -0
  57. data/src/excel/excel_functions/power.rb +18 -0
  58. data/src/excel/excel_functions/round.rb +13 -0
  59. data/src/excel/excel_functions/rounddown.rb +14 -0
  60. data/src/excel/excel_functions/roundup.rb +17 -0
  61. data/src/excel/excel_functions/string_join.rb +19 -0
  62. data/src/excel/excel_functions/subtotal.rb +13 -0
  63. data/src/excel/excel_functions/subtract.rb +18 -0
  64. data/src/excel/excel_functions/sum.rb +8 -0
  65. data/src/excel/excel_functions/sumif.rb +7 -0
  66. data/src/excel/excel_functions/sumifs.rb +74 -0
  67. data/src/excel/excel_functions/sumproduct.rb +32 -0
  68. data/src/excel/excel_functions/vlookup.rb +49 -0
  69. data/src/excel/formula_peg.rb +238 -0
  70. data/src/excel/formula_peg.txt +45 -0
  71. data/src/excel/reference.rb +56 -0
  72. data/src/excel/table.rb +108 -0
  73. data/src/excel_to_code.rb +7 -0
  74. data/src/extract.rb +13 -0
  75. data/src/extract/check_for_unknown_functions.rb +20 -0
  76. data/src/extract/extract_array_formulae.rb +23 -0
  77. data/src/extract/extract_formulae.rb +36 -0
  78. data/src/extract/extract_named_references.rb +38 -0
  79. data/src/extract/extract_relationships.rb +10 -0
  80. data/src/extract/extract_shared_formulae.rb +23 -0
  81. data/src/extract/extract_shared_strings.rb +20 -0
  82. data/src/extract/extract_simple_formulae.rb +18 -0
  83. data/src/extract/extract_table.rb +24 -0
  84. data/src/extract/extract_values.rb +29 -0
  85. data/src/extract/extract_worksheet_dimensions.rb +11 -0
  86. data/src/extract/extract_worksheet_names.rb +10 -0
  87. data/src/extract/extract_worksheet_table_relationships.rb +10 -0
  88. data/src/extract/simple_extract_from_xml.rb +19 -0
  89. data/src/rewrite.rb +10 -0
  90. data/src/rewrite/ast_copy_formula.rb +42 -0
  91. data/src/rewrite/ast_expand_array_formulae.rb +180 -0
  92. data/src/rewrite/rewrite_array_formulae.rb +71 -0
  93. data/src/rewrite/rewrite_array_formulae_to_arrays.rb +18 -0
  94. data/src/rewrite/rewrite_cell_references_to_include_sheet.rb +56 -0
  95. data/src/rewrite/rewrite_formulae_to_ast.rb +24 -0
  96. data/src/rewrite/rewrite_merge_formulae_and_values.rb +18 -0
  97. data/src/rewrite/rewrite_relationship_id_to_filename.rb +22 -0
  98. data/src/rewrite/rewrite_shared_formulae.rb +38 -0
  99. data/src/rewrite/rewrite_values_to_ast.rb +28 -0
  100. data/src/rewrite/rewrite_whole_row_column_references_to_areas.rb +90 -0
  101. data/src/rewrite/rewrite_worksheet_names.rb +20 -0
  102. data/src/simplify.rb +16 -0
  103. data/src/simplify/count_formula_references.rb +58 -0
  104. data/src/simplify/identify_dependencies.rb +56 -0
  105. data/src/simplify/identify_repeated_formula_elements.rb +37 -0
  106. data/src/simplify/inline_formulae.rb +77 -0
  107. data/src/simplify/map_formulae_to_values.rb +157 -0
  108. data/src/simplify/remove_cells.rb +18 -0
  109. data/src/simplify/replace_arrays_with_single_cells.rb +27 -0
  110. data/src/simplify/replace_blanks.rb +58 -0
  111. data/src/simplify/replace_common_elements_in_formulae.rb +19 -0
  112. data/src/simplify/replace_formulae_with_calculated_values.rb +21 -0
  113. data/src/simplify/replace_indirects_with_references.rb +44 -0
  114. data/src/simplify/replace_named_references.rb +82 -0
  115. data/src/simplify/replace_ranges_with_array_literals.rb +54 -0
  116. data/src/simplify/replace_shared_strings.rb +49 -0
  117. data/src/simplify/replace_table_references.rb +71 -0
  118. data/src/simplify/replace_values_with_constants.rb +47 -0
  119. data/src/simplify/simplify_arithmetic.rb +54 -0
  120. data/src/util.rb +2 -0
  121. data/src/util/not_supported_exception.rb +2 -0
  122. data/src/util/try.rb +9 -0
  123. metadata +207 -0
data/README ADDED
@@ -0,0 +1,41 @@
1
+ # excel_to_code
2
+
3
+ Converts some excel spreadsheets (.xlsx, not .xls) into some other programming languages (currently ruby or c).
4
+ This allows the excel spreadsheets to be run programatically, without excel.
5
+
6
+ Its cannonical source is at http://github.com/tamc/excel2code
7
+
8
+ # Running excel_to_code
9
+
10
+ To just have a go:
11
+
12
+ ./bin/excel_to_c <excel_file_name>
13
+
14
+ NB:For small spreadsheets this will take a minute or so. For large spreadsheets it is best to run it overnight.
15
+
16
+ for more detail:
17
+
18
+ ./bin/excel_to_c --compile --run-tests --settable <name of input worksheet> --prune-except <name of output worksheet> <excel file name>
19
+
20
+ this should work:
21
+
22
+ ./bin/excel_to_c --help
23
+
24
+ # Testing excel_to_code
25
+
26
+ 1. Make sure you have ruby 1.9.2 or later installed
27
+ 2. gem install bundler # May need to use sudo
28
+ 3. bundle
29
+ 4. rspec spec/*
30
+
31
+ # Hacking excel_to_code
32
+
33
+ There are some how to guides in the doc folder.
34
+
35
+ # Limitations
36
+
37
+ 1. Not tested at all on Windows
38
+ 2. INDIRECT formula must be convertable at runtime into a standard formula
39
+ 3. Doesn't implement all functions (see doc/Which_functions_are_implemented.md)
40
+ 4. Doesn't implement references that involve range unions and lists
41
+ 5. Sometimes gives cells as being empty, when excel would give the cell as having a numeric value of zero
data/bin/excel_to_c ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require_relative '../src/commands/excel_to_c'
4
+
5
+ command = ExcelToC.new
6
+
7
+ opts = OptionParser.new do |opts|
8
+ opts.banner = <<-END
9
+
10
+ A command to approximately translate excel files into c code.
11
+ Usage: excel_to_c [options] input_excel_file <output_directory>
12
+ input_excel_file is the name of a .xlsx excel file (i.e., produced by Excel 2007+, not an xls file)
13
+ output_directory is the name of a folder in which to place the generated code. If not specified will have the same name as the input_excel_file, without the .xlsx
14
+
15
+ Support: http://github.com/tamc/excel2code
16
+ END
17
+
18
+ opts.separator ""
19
+ opts.separator "Specific options:"
20
+
21
+ opts.on('-s','--settable WORKSHEET','Make it possible to set the values of cells in this worksheet at runtime. By default no values are settable.') do |sheet|
22
+ command.cells_that_can_be_set_at_runtime = { sheet => :all }
23
+ end
24
+
25
+ opts.on('-o','--output-name NAME','Filename to give to c version of code (and associated ruby interface). Defaults to a folder with the same name as the excel file.') do |name|
26
+ command.output_name = name
27
+ end
28
+
29
+ opts.on('-p','--prune-except WORKSHEET',"Remove all cells except those on this worksheet, or that are required to calculate values on that worksheet. By default keeps all cells.") do |sheet|
30
+ command.cells_to_keep = { sheet => :all }
31
+ end
32
+
33
+ opts.on('-c','--compile',"Compile the generated c code") do
34
+ command.actually_compile_c_code = true
35
+ end
36
+
37
+ opts.on('-r','--run-tests',"Compile the generated c code and then run the tests") do
38
+ command.actually_compile_c_code = true
39
+ command.actually_run_tests = true
40
+ end
41
+
42
+ opts.on("-h", "--help", "Show this message") do
43
+ puts opts
44
+ exit
45
+ end
46
+ end
47
+
48
+ begin
49
+ opts.parse!(ARGV)
50
+ rescue OptionParser::ParseError => e
51
+ STDERR.puts e.message, "\n", opts
52
+ exit(-1)
53
+ end
54
+
55
+ unless ARGV.size > 0
56
+ puts opts
57
+ exit(-1)
58
+ end
59
+
60
+ command.excel_file = ARGV[0]
61
+ command.output_directory = ARGV[1] if ARGV[1]
62
+
63
+ command.go!
data/bin/excel_to_ruby ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../src/commands/excel_to_ruby'
3
+ command = ExcelToRuby.new
4
+ command.excel_file = ARGV[0]
5
+ command.compiled_module_name = "ExcelSpreadsheet"
6
+ command.output_directory = ARGV[1]
7
+ command.values_that_can_be_set_at_runtime = { ARGV[2] => :all } if ARGV[2]
8
+ command.outputs_to_keep = { ARGV[3] => :all } if ARGV[3]
9
+ command.go!
data/src/commands.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative "commands/excel_to_ruby"
2
+ require_relative "commands/excel_to_c"
@@ -0,0 +1,858 @@
1
+ # coding: utf-8
2
+
3
+ require 'fileutils'
4
+ require_relative '../util'
5
+ require_relative '../excel'
6
+ require_relative '../extract'
7
+ require_relative '../rewrite'
8
+ require_relative '../simplify'
9
+ require_relative '../compile'
10
+
11
+ # Used to throw normally fatal errors
12
+ class ExcelToCException < Exception; end
13
+
14
+ class ExcelToC
15
+
16
+ # Required attribute. The source excel file. This must be .xlsx not .xls
17
+ attr_accessor :excel_file
18
+
19
+ # Optional attribute. The output directory.
20
+ # If not specified, will be '#{excel_file_name}/c'
21
+ attr_accessor :output_directory
22
+
23
+ # Optional attribute. The name of the resulting c file (and associated ruby ffi module). Defaults to excelspreadsheet
24
+ attr_accessor :output_name
25
+
26
+ # Optional attribute. The excel file will be translated to xml and stored here.
27
+ # If not specified, will be '#{excel_file_name}/xml'
28
+ attr_accessor :xml_directory
29
+
30
+ # Optional attribute. The intermediate workings will be stored here.
31
+ # If not specified, will be '#{excel_file_name}/intermediate'
32
+ attr_accessor :intermediate_directory
33
+
34
+ # Optional attribute. Specifies which cells have setters created in the c code so their values can be altered at runtime.
35
+ # It is a hash. The keys are the sheet names. The values are either the symbol :all to specify that all cells on that sheet
36
+ # should be setable, or an array of cell names on that sheet that should be settable (e.g., A1)
37
+ attr_accessor :cells_that_can_be_set_at_runtime
38
+
39
+ # Optional attribute. Specifies which cells must appear in the final generated code.
40
+ # The default is that all cells in the original spreadsheet appear in the final code.
41
+ #
42
+ # If specified, then any cells that are not:
43
+ # * specified
44
+ # * required to calculate one of the cells that is specified
45
+ # * specified as a cell that can be set at runtime
46
+ # may be excluded from the final generated code.
47
+ #
48
+ # It is a hash. The keys are the sheet names. The values are either the symbol :all to specify that all cells on that sheet
49
+ # should be lept, or an array of cell names on that sheet that should be kept (e.g., A1)
50
+ attr_accessor :cells_to_keep
51
+
52
+ # Optional attribute. Boolean.
53
+ # * true - the generated c code is compiled
54
+ # * false - the generated c code is not compiled (default, unless actuall_run_tests is specified as true)
55
+ attr_accessor :actually_compile_c_code
56
+
57
+ # Optional attribute. Boolean.
58
+ # * true - the generated tests are run
59
+ # * false - the generated tests are not run
60
+ attr_accessor :actually_run_tests
61
+
62
+ def set_defaults
63
+ raise ExcelToCException.new("No excel file has been specified in ExcelToC.excel_file") unless excel_file
64
+
65
+ self.output_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'c')
66
+ self.xml_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'xml')
67
+ self.intermediate_directory ||= File.join(File.dirname(excel_file),File.basename(excel_file,".*"),'intermediate')
68
+
69
+ self.output_name ||= "Excelspreadsheet"
70
+
71
+ self.cells_that_can_be_set_at_runtime ||= {}
72
+
73
+ # Make sure that all the cell names are downcase and don't have any $ in them
74
+ cells_that_can_be_set_at_runtime.keys.each do |sheet|
75
+ next unless cells_that_can_be_set_at_runtime[sheet].is_a?(Array)
76
+ cells_that_can_be_set_at_runtime[sheet] = cells_that_can_be_set_at_runtime[sheet].map { |reference| reference.gsub('$','').downcase }
77
+ end
78
+
79
+ if cells_to_keep
80
+ cells_to_keep.keys.each do |sheet|
81
+ next unless cells_to_keep[sheet].is_a?(Array)
82
+ cells_to_keep[sheet] = cells_to_keep[sheet].map { |reference| reference.gsub('$','').downcase }
83
+ end
84
+ end
85
+
86
+ self.excel_file = File.expand_path(excel_file)
87
+ self.output_directory = File.expand_path(output_directory)
88
+
89
+ end
90
+
91
+ def go!
92
+ # This sorts out the attributes
93
+ set_defaults
94
+
95
+ # These turn the excel into a more accesible format
96
+ sort_out_output_directories
97
+ unzip_excel
98
+
99
+ # These get all the information out of the excel and put
100
+ # into a useful format
101
+ extract_data_from_workbook
102
+ extract_data_from_worksheets
103
+ merge_table_files
104
+ rewrite_worksheets
105
+
106
+ # These perform a series of transformations to the information
107
+ # with the intent of removing any redundant calculations
108
+ # that are in the excel
109
+ simplify_worksheets
110
+ optimise_and_replace_indirect_loop
111
+ replace_blanks
112
+ remove_any_cells_not_needed_for_outputs
113
+ inline_formulae_that_are_only_used_once
114
+ separate_formulae_elements
115
+
116
+ # These actually create the code version of the excel
117
+ write_out_excel_as_code
118
+ write_build_script
119
+ write_fuby_ffi_interface
120
+ write_tests
121
+
122
+ # These compile and run the code version of the excel
123
+ compile_c_code
124
+ run_tests
125
+
126
+ puts
127
+ puts "The generated code is available in #{File.join(output_directory)}"
128
+ end
129
+
130
+ def sort_out_output_directories
131
+ FileUtils.mkdir_p(output_directory)
132
+ FileUtils.mkdir_p(xml_directory)
133
+ FileUtils.mkdir_p(intermediate_directory)
134
+ end
135
+
136
+ def unzip_excel
137
+ puts `rm -fr '#{xml_directory}'`
138
+ puts `unzip -uo '#{excel_file}' -d '#{xml_directory}'`
139
+ end
140
+
141
+ def extract_data_from_workbook
142
+ extract_shared_strings
143
+
144
+ extract ExtractNamedReferences, 'workbook.xml', 'named_references'
145
+ rewrite RewriteFormulaeToAst, 'named_references', 'named_references.ast'
146
+
147
+ extract ExtractWorksheetNames, 'workbook.xml', 'worksheet_names_without_filenames'
148
+ extract ExtractRelationships, File.join('_rels','workbook.xml.rels'), 'workbook_relationships'
149
+ rewrite RewriteWorksheetNames, 'worksheet_names_without_filenames', 'workbook_relationships', 'worksheet_names'
150
+ rewrite MapSheetNamesToCNames, 'worksheet_names', 'worksheet_c_names'
151
+
152
+ extract_dimensions_from_worksheets
153
+ end
154
+
155
+ def extract_shared_strings
156
+ if File.exists?(File.join(xml_directory,'xl','sharedStrings.xml'))
157
+ extract ExtractSharedStrings, 'sharedStrings.xml', 'shared_strings'
158
+ else
159
+ FileUtils.touch(File.join(intermediate_directory,'shared_strings'))
160
+ end
161
+ end
162
+
163
+ def extract_dimensions_from_worksheets
164
+ dimension_file = intermediate('dimensions')
165
+ worksheets("Extracting dimensions") do |name,xml_filename|
166
+ dimension_file.write name
167
+ dimension_file.write "\t"
168
+ extract ExtractWorksheetDimensions, File.open(xml_filename,'r'), dimension_file
169
+ end
170
+ dimension_file.close
171
+ end
172
+
173
+ def extract_data_from_worksheets
174
+ worksheets("Initial data extract") do |name,xml_filename|
175
+ worksheet_directory = File.join(intermediate_directory,name)
176
+ FileUtils.mkdir_p(worksheet_directory)
177
+ worksheet_xml = File.open(xml_filename,'r')
178
+ { ExtractValues => 'values',
179
+ ExtractSimpleFormulae => 'simple_formulae',
180
+ ExtractSharedFormulae => 'shared_formulae',
181
+ ExtractArrayFormulae => 'array_formulae'
182
+ }.each do |_klass,output_filename|
183
+ worksheet_xml.rewind
184
+ extract _klass, worksheet_xml, File.join(name,output_filename)
185
+ if _klass == ExtractValues
186
+ rewrite RewriteValuesToAst, File.join(name,output_filename), File.join(name,"#{output_filename}.ast")
187
+ else
188
+ rewrite RewriteFormulaeToAst, File.join(name,output_filename), File.join(name,"#{output_filename}.ast")
189
+ end
190
+ end
191
+ worksheet_xml.rewind
192
+ extract ExtractWorksheetTableRelationships, worksheet_xml, File.join(name,'table_rids')
193
+ if File.exists?(File.join(xml_directory,'xl','worksheets','_rels',"#{File.basename(xml_filename)}.rels"))
194
+ extract ExtractRelationships, File.join('worksheets','_rels',"#{File.basename(xml_filename)}.rels"), File.join(name,'relationships')
195
+ rewrite RewriteRelationshipIdToFilename, File.join(name,'table_rids'), File.join(name,'relationships'), File.join(name,'table_filenames')
196
+ tables = intermediate(name,'tables')
197
+ table_extractor = ExtractTable.new(name)
198
+ table_filenames = input(name,'table_filenames')
199
+ table_filenames.lines.each do |line|
200
+ extract table_extractor, File.join('worksheets',line.strip), tables
201
+ end
202
+ close(tables,table_filenames)
203
+ else
204
+ FileUtils.touch File.join(intermediate_directory,name,'relationships')
205
+ FileUtils.touch File.join(intermediate_directory,name,'table_filenames')
206
+ FileUtils.touch File.join(intermediate_directory,name,'tables')
207
+ end
208
+ close(worksheet_xml)
209
+ end
210
+ end
211
+
212
+ def rewrite_worksheets
213
+ worksheets("Initial rewrite of references and formulae") do |name,xml_filename|
214
+ rewrite_row_and_column_references(name,xml_filename)
215
+ rewrite_shared_formulae(name,xml_filename)
216
+ rewrite_array_formulae(name,xml_filename)
217
+ combine_formulae_files(name,xml_filename)
218
+ end
219
+ end
220
+
221
+ def rewrite_row_and_column_references(name,xml_filename)
222
+ dimensions = input('dimensions')
223
+ %w{simple_formulae.ast shared_formulae.ast array_formulae.ast}.each do |file|
224
+ dimensions.rewind
225
+ i = File.open(File.join(intermediate_directory,name,file),'r')
226
+ o = File.open(File.join(intermediate_directory,name,"#{file}-nocols"),'w')
227
+ RewriteWholeRowColumnReferencesToAreas.rewrite(i,name, dimensions, o)
228
+ close(i,o)
229
+ end
230
+ dimensions.close
231
+ end
232
+
233
+ def rewrite_shared_formulae(name,xml_filename)
234
+ i = File.open(File.join(intermediate_directory,name,'shared_formulae.ast-nocols'),'r')
235
+ o = File.open(File.join(intermediate_directory,name,"shared_formulae-expanded.ast"),'w')
236
+ RewriteSharedFormulae.rewrite(i,o)
237
+ close(i,o)
238
+ end
239
+
240
+ def rewrite_array_formulae(name,xml_filename)
241
+ r = ReplaceNamedReferences.new
242
+ r.sheet_name = name
243
+ replace r, File.join(name,'array_formulae.ast-nocols'), 'named_references.ast', File.join(name,"array_formulae1.ast")
244
+
245
+ r = ReplaceTableReferences.new
246
+ r.sheet_name = name
247
+ replace r, File.join(name,'array_formulae1.ast'), 'all_tables', File.join(name,"array_formulae2.ast")
248
+ replace SimplifyArithmetic, File.join(name,'array_formulae2.ast'), File.join(name,'array_formulae3.ast')
249
+ replace ReplaceRangesWithArrayLiterals, File.join(name,"array_formulae3.ast"), File.join(name,"array_formulae4.ast")
250
+ rewrite RewriteArrayFormulaeToArrays, File.join(name,"array_formulae4.ast"), File.join(name,"array_formulae5.ast")
251
+ rewrite RewriteArrayFormulae, File.join(name,'array_formulae5.ast'), File.join(name,"array_formulae-expanded.ast")
252
+ end
253
+
254
+ def combine_formulae_files(name,xml_filename)
255
+ values = File.join(name,'values.ast')
256
+ shared_formulae = File.join(name,"shared_formulae-expanded.ast")
257
+ array_formulae = File.join(name,"array_formulae-expanded.ast")
258
+ simple_formulae = File.join(name,"simple_formulae.ast-nocols")
259
+ output = File.join(name,'formulae.ast')
260
+ rewrite RewriteMergeFormulaeAndValues, values, shared_formulae, array_formulae, simple_formulae, output
261
+ end
262
+
263
+ def merge_table_files
264
+ tables = []
265
+ worksheets("Merging table files") do |name,xml_filename|
266
+ tables << File.join(intermediate_directory,name,'tables')
267
+ end
268
+ `sort #{tables.map { |t| " '#{t}' "}.join} > #{File.join(intermediate_directory,'all_tables')}`
269
+ end
270
+
271
+ def simplify_worksheets
272
+ worksheets("Simplifying") do |name,xml_filename|
273
+ simplify_worksheet(name,xml_filename)
274
+ end
275
+ end
276
+
277
+ def simplify_worksheet(name,xml_filename)
278
+ replace SimplifyArithmetic, File.join(name,'formulae.ast'), File.join(name,'formulae_simple_arithmetic.ast')
279
+ replace ReplaceSharedStrings, File.join(name,'formulae_simple_arithmetic.ast'), 'shared_strings', File.join(name,"formulae_no_shared_strings.ast")
280
+ replace ReplaceSharedStrings, File.join(name,'values.ast'), 'shared_strings', File.join(name,"values_no_shared_strings.ast")
281
+ r = ReplaceNamedReferences.new
282
+ r.sheet_name = name
283
+ replace r, File.join(name,'formulae_no_shared_strings.ast'), 'named_references.ast', File.join(name,"formulae_no_named_references.ast")
284
+
285
+ r = ReplaceTableReferences.new
286
+ r.sheet_name = name
287
+ replace r, File.join(name,'formulae_no_named_references.ast'), 'all_tables', File.join(name,"formulae_no_table_references.ast")
288
+ replace ReplaceRangesWithArrayLiterals, File.join(name,"formulae_no_table_references.ast"), File.join(name,"formulae_no_ranges.ast")
289
+ end
290
+
291
+ def replace_blanks
292
+ references = {}
293
+ worksheets("Loading formulae") do |name,xml_filename|
294
+ r = references[name] = {}
295
+ i = input(name,"formulae_no_indirects_optimised.ast")
296
+ i.lines do |line|
297
+ ref = line[/^(.*?)\t/,1]
298
+ r[ref] = true
299
+ end
300
+ end
301
+ worksheets("Replacing blanks") do |name,xml_filename|
302
+ #fork do
303
+ r = ReplaceBlanks.new
304
+ r.references = references
305
+ r.default_sheet_name = name
306
+ replace r, File.join(name,"formulae_no_indirects_optimised.ast"),File.join(name,"formulae_no_blanks.ast")
307
+ #end
308
+ end
309
+ end
310
+
311
+ def optimise_and_replace_indirect_loop
312
+ number_of_loops = 4
313
+ 1.upto(number_of_loops) do |pass|
314
+ puts "Optimise and replace indirects pass #{pass}"
315
+ start = pass == 1 ? "formulae_no_ranges.ast" : "optimse-output-#{pass-1}.ast"
316
+ finish = pass == number_of_loops ? "formulae_no_indirects_optimised.ast" : "optimse-output-#{pass}.ast"
317
+ replace_indirects(start,"replace-indirect-output-#{pass}.ast","replace-indirect-working-#{pass}-")
318
+ optimise_sheets("replace-indirect-output-#{pass}.ast",finish,"optimse-working-#{pass}-")
319
+ end
320
+ end
321
+
322
+ def replace_indirects(start_filename,finish_filename,basename)
323
+ worksheets("Replacing indirects") do |name,xml_filename|
324
+ counter = 1
325
+ replace ReplaceIndirectsWithReferences, File.join(name,start_filename), File.join(name,"#{basename}#{counter+1}.ast")
326
+ counter += 1
327
+
328
+ r = ReplaceNamedReferences.new
329
+ r.sheet_name = name
330
+ replace r, File.join(name,"#{basename}#{counter}.ast"), 'named_references.ast', File.join(name,"#{basename}#{counter+1}.ast")
331
+ counter += 1
332
+
333
+ r = ReplaceTableReferences.new
334
+ r.sheet_name = name
335
+ replace r, File.join(name,"#{basename}#{counter}.ast"), 'all_tables', File.join(name,"#{basename}#{counter+1}.ast")
336
+ counter += 1
337
+
338
+ replace ReplaceRangesWithArrayLiterals, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
339
+ counter += 1
340
+ replace ReplaceArraysWithSingleCells, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
341
+ counter += 1
342
+
343
+ # Finally, create the output directory
344
+ i = File.join(intermediate_directory,name,"#{basename}#{counter}.ast")
345
+ o = File.join(intermediate_directory,name,finish_filename)
346
+ `cp '#{i}' '#{o}'`
347
+ end
348
+ end
349
+
350
+ def optimise_sheets(start_filename,finish_filename,basename)
351
+ counter = 1
352
+
353
+ # Setup start
354
+ worksheets("Setting up for optimise -#{counter}") do |name|
355
+ i = File.join(intermediate_directory,name,start_filename)
356
+ o = File.join(intermediate_directory,name,"#{basename}#{counter}.ast")
357
+ `cp '#{i}' '#{o}'`
358
+ end
359
+
360
+ worksheets("Replacing with calculated values #{counter}-#{counter+1}") do |name,xml_filename|
361
+ #fork do
362
+ replace ReplaceFormulaeWithCalculatedValues, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
363
+ #end
364
+ end
365
+ counter += 1
366
+ Process.waitall
367
+
368
+ references = all_formulae("#{basename}#{counter}.ast")
369
+ inline_ast_decision = lambda do |sheet,cell,references|
370
+ references_to_keep = @cells_that_can_be_set_at_runtime[sheet]
371
+ if references_to_keep && (references_to_keep == :all || references_to_keep.include?(cell))
372
+ false
373
+ else
374
+ ast = references[sheet][cell]
375
+ if ast
376
+ if [:number,:string,:blank,:null,:error,:boolean_true,:boolean_false,:sheet_reference,:cell].include?(ast.first)
377
+ # puts "Inlining #{sheet}.#{cell}: #{ast.inspect}"
378
+ true
379
+ else
380
+ false
381
+ end
382
+ else
383
+ true # Always inline blanks
384
+ end
385
+ end
386
+ end
387
+ r = InlineFormulae.new
388
+ r.references = references
389
+ r.inline_ast = inline_ast_decision
390
+
391
+ worksheets("Inlining formulae #{counter}-#{counter+1}") do |name,xml_filename|
392
+ #fork do
393
+ r.default_sheet_name = name
394
+ replace r, File.join(name,"#{basename}#{counter}.ast"), File.join(name,"#{basename}#{counter+1}.ast")
395
+ #end
396
+ end
397
+ counter += 1
398
+ Process.waitall
399
+
400
+ # Finish
401
+ worksheets("Moving sheets #{counter}-") do |name|
402
+ o = File.join(intermediate_directory,name,finish_filename)
403
+ i = File.join(intermediate_directory,name,"#{basename}#{counter}.ast")
404
+ `cp '#{i}' '#{o}'`
405
+ end
406
+ end
407
+
408
+ def remove_any_cells_not_needed_for_outputs(formula_in = "formulae_no_blanks.ast", formula_out = "formulae_pruned.ast", values_in = "values_no_shared_strings.ast", values_out = "values_pruned.ast")
409
+ if cells_to_keep && !cells_to_keep.empty?
410
+ identifier = IdentifyDependencies.new
411
+ identifier.references = all_formulae(formula_in)
412
+ cells_to_keep.each do |sheet_to_keep,cells_to_keep|
413
+ if cells_to_keep == :all
414
+ identifier.add_depedencies_for(sheet_to_keep)
415
+ elsif cells_to_keep.is_a?(Array)
416
+ cells_to_keep.each do |cell|
417
+ identifier.add_depedencies_for(sheet_to_keep,cell)
418
+ end
419
+ end
420
+ end
421
+ r = RemoveCells.new
422
+ worksheets("Removing cells") do |name,xml_filename|
423
+ #fork do
424
+ r.cells_to_keep = identifier.dependencies[name]
425
+ rewrite r, File.join(name, formula_in), File.join(name, formula_out)
426
+ rewrite r, File.join(name, values_in), File.join(name, values_out)
427
+ #end
428
+ end
429
+ Process.waitall
430
+ else
431
+ worksheets do |name,xml_filename|
432
+ i = File.join(intermediate_directory,name, formula_in)
433
+ o = File.join(intermediate_directory,name, formula_out)
434
+ `cp '#{i}' '#{o}'`
435
+ i = File.join(intermediate_directory,name, values_in)
436
+ o = File.join(intermediate_directory,name, values_out)
437
+ `cp '#{i}' '#{o}'`
438
+ end
439
+ end
440
+ end
441
+
442
+ def inline_formulae_that_are_only_used_once
443
+ references = all_formulae("formulae_pruned.ast")
444
+ counter = CountFormulaReferences.new
445
+ count = counter.count(references)
446
+
447
+ inline_ast_decision = lambda do |sheet,cell,references|
448
+ references_to_keep = @cells_that_can_be_set_at_runtime[sheet]
449
+ if references_to_keep && (references_to_keep == :all || references_to_keep.include?(cell))
450
+ false
451
+ else
452
+ count[sheet][cell] == 1
453
+ end
454
+ end
455
+
456
+ r = InlineFormulae.new
457
+ r.references = references
458
+ r.inline_ast = inline_ast_decision
459
+
460
+ worksheets("Inlining formulae") do |name,xml_filename|
461
+ #fork do
462
+ r.default_sheet_name = name
463
+ replace r, File.join(name,"formulae_pruned.ast"), File.join(name,"formulae_inlined.ast")
464
+ #end
465
+ end
466
+
467
+ remove_any_cells_not_needed_for_outputs("formulae_inlined.ast", "formulae_inlined_pruned.ast", "values_pruned.ast", "values_pruned2.ast")
468
+
469
+ end
470
+
471
+ def separate_formulae_elements
472
+ # First we add the sheet to all references, so that we can then look for common elements accross worksheets
473
+ r = RewriteCellReferencesToIncludeSheet.new
474
+ worksheets("Adding the sheet to all references") do |name,xml_filename|
475
+ r.worksheet = name
476
+ rewrite r, File.join(name,"formulae_inlined_pruned.ast"), File.join(name,"formulae_inlined_pruned_with_sheets.ast")
477
+ end
478
+
479
+ references = all_formulae("formulae_inlined_pruned_with_sheets.ast")
480
+ identifier = IdentifyRepeatedFormulaElements.new
481
+ repeated_elements = identifier.count(references)
482
+ repeated_elements.delete_if do |element,count|
483
+ count < 2
484
+ end
485
+ o = intermediate('common-elements-1.ast')
486
+ i = 0
487
+ repeated_elements.each do |element,count|
488
+ o.puts "common#{i}\t#{element}"
489
+ i = i + 1
490
+ end
491
+ close(o)
492
+
493
+ worksheets("Replacing repeated elements") do |name,xml_filename|
494
+ replace ReplaceCommonElementsInFormulae, File.join(name,"formulae_inlined_pruned_with_sheets.ast"), "common-elements-1.ast", File.join(name,"formulae_inlined_pruned_replaced-1.ast")
495
+ end
496
+
497
+ r = ReplaceValuesWithConstants.new
498
+ worksheets("Replacing values with constants") do |name,xml_filename|
499
+ i = input(name,"formulae_inlined_pruned_replaced-1.ast")
500
+ o = intermediate(name,"formulae_inlined_pruned_replaced.ast")
501
+ r.replace(i,o)
502
+ close(i,o)
503
+ end
504
+
505
+ puts "Replacing values with constants in common elements"
506
+ i = input("common-elements-1.ast")
507
+ o = intermediate("common-elements.ast")
508
+ r.replace(i,o)
509
+ close(i,o)
510
+
511
+ puts "Writing out constants"
512
+ co = intermediate("value_constants.ast")
513
+ r.rewriter.constants.each do |ast,constant|
514
+ co.puts "#{constant}\t#{ast}"
515
+ end
516
+ close(co)
517
+ end
518
+
519
+ def write_out_excel_as_code
520
+
521
+ all_refs = all_formulae("formulae_inlined_pruned_replaced.ast")
522
+
523
+ number_of_refs = 0
524
+
525
+ # Probably a better way of getting the runtime file to be compiled with the created file
526
+ puts `cp #{File.join(File.dirname(__FILE__),'..','compile','c','excel_to_c_runtime.c')} #{File.join(output_directory,'excel_to_c_runtime.c')}`
527
+
528
+ # Output the workbook preamble
529
+ w = input("worksheet_c_names")
530
+ o = output("#{output_name.downcase}.c")
531
+ o.puts "// #{excel_file} approximately translated into C"
532
+ o.puts '#include "excel_to_c_runtime.c"'
533
+ o.puts
534
+
535
+ # Now we have to put all the initial definitions out
536
+ o.puts "// definitions"
537
+
538
+ i = input("common-elements.ast")
539
+ c = CompileToCHeader.new
540
+ c.gettable = lambda { |ref| false }
541
+ c.rewrite(i,w,o)
542
+ i.rewind
543
+ number_of_refs += i.lines.to_a.size
544
+ close(i)
545
+
546
+ worksheets("Compiling definitions") do |name,xml_filename|
547
+ w.rewind
548
+ c = CompileToCHeader.new
549
+ c.settable = settable(name)
550
+ c.gettable = gettable(name)
551
+ c.worksheet = name
552
+ i = input(name,"formulae_inlined_pruned_replaced.ast")
553
+ c.rewrite(i,w,o)
554
+ i.rewind
555
+ number_of_refs += i.lines.to_a.size
556
+ close(i)
557
+ end
558
+
559
+ o.puts "// end of definitions"
560
+ o.puts
561
+ o.puts "// Used to decide whether to recalculate a cell"
562
+ o.puts "static int variable_set[#{number_of_refs}];"
563
+ o.puts "void reset() {"
564
+ o.puts " int i;"
565
+ o.puts " cell_counter = 0;"
566
+ o.puts " for(i = 0; i < #{number_of_refs}; i++) {"
567
+ o.puts " variable_set[i] = 0;"
568
+ o.puts " }"
569
+ o.puts "};"
570
+ o.puts
571
+
572
+ # Output the value constants
573
+ o.puts "// starting the value constants"
574
+ mapper = MapValuesToCStructs.new
575
+ i = input("value_constants.ast")
576
+ i.lines do |line|
577
+ begin
578
+ ref, formula = line.split("\t")
579
+ ast = eval(formula)
580
+ calculation = mapper.map(ast)
581
+ o.puts "static ExcelValue #{ref} = #{calculation};"
582
+ rescue Exception => e
583
+ puts "Exception at line #{line}"
584
+ raise
585
+ end
586
+ end
587
+ close(i)
588
+ o.puts "// ending the value constants"
589
+ o.puts
590
+
591
+ variable_set_counter = 0
592
+
593
+ # output the common elements
594
+ o.puts "// starting common elements"
595
+ w.rewind
596
+ c = CompileToC.new
597
+ c.variable_set_counter = variable_set_counter
598
+ c.gettable = lambda { |ref| false }
599
+ i = input("common-elements.ast")
600
+ c.rewrite(i,w,o)
601
+ close(i)
602
+ o.puts "// ending common elements"
603
+ o.puts
604
+
605
+ variable_set_counter = c.variable_set_counter
606
+
607
+ c = CompileToC.new
608
+ c.variable_set_counter = variable_set_counter
609
+ # Output the elements from each worksheet in turn
610
+ worksheets("Compiling worksheet") do |name,xml_filename|
611
+ w.rewind
612
+ c.settable = settable(name)
613
+ c.gettable = gettable(name)
614
+ c.worksheet = name
615
+
616
+ i = input(name,"formulae_inlined_pruned_replaced.ast")
617
+ ruby_name = c_name_for_worksheet_name(name)
618
+ o.puts "// start #{name}"
619
+ c.rewrite(i,w,o)
620
+ o.puts "// end #{name}"
621
+ o.puts
622
+ close(i)
623
+ end
624
+ close(w,o)
625
+ end
626
+
627
+ def settable(name)
628
+ settable_refs = @cells_that_can_be_set_at_runtime[name]
629
+ if settable_refs
630
+ lambda { |ref| (settable_refs == :all) ? true : settable_refs.include?(ref) }
631
+ else
632
+ lambda { |ref| false }
633
+ end
634
+ end
635
+
636
+ def gettable(name)
637
+ if @cells_to_keep
638
+ gettable_refs = @cells_to_keep[name]
639
+ if gettable_refs
640
+ lambda { |ref| (gettable_refs == :all) ? true : gettable_refs.include?(ref) }
641
+ else
642
+ lambda { |ref| false }
643
+ end
644
+ else
645
+ lambda { |ref| true }
646
+ end
647
+ end
648
+
649
+ def write_build_script
650
+ o = output("Makefile")
651
+ name = output_name.downcase
652
+
653
+ # Target for shared library
654
+ o.puts "lib#{name}.dylib: #{name}.o"
655
+ o.puts "\tgcc -shared -o lib#{name}.dylib #{name}.o"
656
+ o.puts
657
+
658
+ # Target for compiled version
659
+ o.puts "#{name}.o:"
660
+ o.puts "\tgcc -Wall -fPIC -c #{name}.c"
661
+ o.puts
662
+
663
+ # Target for cleaning
664
+ o.puts "clean:"
665
+ o.puts "\trm #{name}.o"
666
+ o.puts "\trm lib#{name}.dylib"
667
+
668
+ close(o)
669
+ end
670
+
671
+ def write_fuby_ffi_interface
672
+ all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
673
+ name = output_name.downcase
674
+ o = output("#{name}.rb")
675
+ code = <<END
676
+ require 'ffi'
677
+
678
+ module #{name.capitalize}
679
+ extend FFI::Library
680
+ ffi_lib File.join(File.dirname(__FILE__),'lib#{name}.dylib')
681
+ ExcelType = enum :ExcelEmpty, :ExcelNumber, :ExcelString, :ExcelBoolean, :ExcelError, :ExcelRange
682
+
683
+ class ExcelValue < FFI::Struct
684
+ layout :type, ExcelType,
685
+ :number, :double,
686
+ :string, :string,
687
+ :array, :pointer,
688
+ :rows, :int,
689
+ :columns, :int
690
+ end
691
+
692
+ END
693
+ o.puts code
694
+ o.puts
695
+ o.puts " # use this function to reset all cell values"
696
+ o.puts " attach_function 'reset', [], :void"
697
+
698
+ worksheets("Adding references to ruby shim for") do |name,xml_filename|
699
+ o.puts
700
+ o.puts " # start of #{name}"
701
+ c_name = c_name_for_worksheet_name(name)
702
+
703
+ # Put in place the setters, if any
704
+ settable_refs = @cells_that_can_be_set_at_runtime[name]
705
+ if settable_refs
706
+ settable_refs = all_formulae[name].keys if settable_refs == :all
707
+ settable_refs.each do |ref|
708
+ o.puts " attach_function 'set_#{c_name}_#{ref.downcase}', [ExcelValue.by_value], :void"
709
+ end
710
+ end
711
+
712
+ if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
713
+ getable_refs = all_formulae[name].keys
714
+ elsif !cells_to_keep[name] && settable_refs
715
+ getable_refs = settable_refs
716
+ else
717
+ getable_refs = cells_to_keep[name] || []
718
+ end
719
+
720
+ getable_refs.each do |ref|
721
+ o.puts " attach_function '#{c_name}_#{ref.downcase}', [], ExcelValue.by_value"
722
+ end
723
+
724
+ o.puts " # end of #{name}"
725
+ end
726
+ o.puts "end"
727
+ close(o)
728
+ end
729
+
730
+ def write_tests
731
+ name = output_name.downcase
732
+ o = output("#{name}_test.rb")
733
+ o.puts "# coding: utf-8"
734
+ o.puts "# Test for #{name}"
735
+ o.puts "require 'rubygems'"
736
+ o.puts "gem 'minitest'"
737
+ o.puts "require 'test/unit'"
738
+ o.puts "require_relative '#{output_name.downcase}'"
739
+ o.puts
740
+ o.puts "class Test#{name.capitalize} < Test::Unit::TestCase"
741
+ o.puts " def spreadsheet; @spreadsheet ||= init_spreadsheet; end"
742
+ o.puts " def init_spreadsheet; #{name.capitalize} end"
743
+
744
+ all_formulae = all_formulae('formulae_inlined_pruned_replaced.ast')
745
+
746
+ worksheets("Adding tests for") do |name,xml_filename|
747
+ i = input(name,"values_pruned2.ast")
748
+ o.puts
749
+ o.puts " # start of #{name}"
750
+ c_name = c_name_for_worksheet_name(name)
751
+ if !cells_to_keep || cells_to_keep.empty? || cells_to_keep[name] == :all
752
+ refs_to_test = all_formulae[name].keys
753
+ else
754
+ refs_to_test = cells_to_keep[name]
755
+ end
756
+ if refs_to_test && !refs_to_test.empty?
757
+ CompileToCUnitTest.rewrite(i, c_name, refs_to_test, o)
758
+ end
759
+ close(i)
760
+ end
761
+ o.puts "end"
762
+ close(o)
763
+ end
764
+
765
+ def compile_c_code
766
+ return unless actually_compile_c_code || actually_run_tests
767
+ puts "Compiling the resulting c code"
768
+ puts `cd #{File.join(output_directory)}; make clean; make`
769
+ end
770
+
771
+ def run_tests
772
+ return unless actually_run_tests
773
+ puts "Running the resulting tests"
774
+ puts `cd #{File.join(output_directory)}; ruby "#{output_name.downcase}_test.rb"`
775
+ end
776
+
777
+
778
+ # UTILITY FUNCTIONS
779
+
780
+ def all_formulae(filename)
781
+ references = {}
782
+ worksheets do |name,xml_filename|
783
+ r = references[name] = {}
784
+ i = input(name,filename)
785
+ i.lines do |line|
786
+ line =~ /^(.*?)\t(.*)$/
787
+ ref, ast = $1, $2
788
+ r[$1] = eval($2)
789
+ end
790
+ end
791
+ references
792
+ end
793
+
794
+ def c_name_for_worksheet_name(name)
795
+ unless @worksheet_names
796
+ w = input("worksheet_c_names")
797
+ @worksheet_names = Hash[w.readlines.map { |line| line.split("\t").map { |a| a.strip }}]
798
+ close(w)
799
+ end
800
+ @worksheet_names[name]
801
+ end
802
+
803
+ def worksheets(message = "Processing",&block)
804
+ IO.readlines(File.join(intermediate_directory,'worksheet_names')).each do |line|
805
+ name, filename = *line.split("\t")
806
+ filename = File.expand_path(File.join(xml_directory,'xl',filename.strip))
807
+ puts "#{message} #{name}"
808
+ block.call(name, filename)
809
+ end
810
+ end
811
+
812
+ def extract(_klass,xml_name,output_name)
813
+ i = xml_name.is_a?(String) ? xml(xml_name) : xml_name
814
+ o = output_name.is_a?(String) ? intermediate(output_name) : output_name
815
+ _klass.extract(i,o)
816
+ if xml_name.is_a?(String)
817
+ close(i)
818
+ end
819
+ if output_name.is_a?(String)
820
+ close(o)
821
+ end
822
+ end
823
+
824
+ def rewrite(_klass,*args)
825
+ o = intermediate(args.pop)
826
+ inputs = args.map { |name| input(name) }
827
+ _klass.rewrite(*inputs,o)
828
+ close(*inputs,o)
829
+ end
830
+
831
+ def replace(_klass,*args)
832
+ o = intermediate(args.pop)
833
+ inputs = args.map { |name| input(name) }
834
+ _klass.replace(*inputs,o)
835
+ close(*inputs,o)
836
+ end
837
+
838
+ def xml(*args)
839
+ File.open(File.join(xml_directory,'xl',*args),'r')
840
+ end
841
+
842
+ def input(*args)
843
+ File.open(File.join(intermediate_directory,*args),'r')
844
+ end
845
+
846
+ def intermediate(*args)
847
+ File.open(File.join(intermediate_directory,*args),'w')
848
+ end
849
+
850
+ def output(*args)
851
+ File.open(File.join(output_directory,*args),'w')
852
+ end
853
+
854
+ def close(*args)
855
+ args.map(&:close)
856
+ end
857
+
858
+ end