rubyfromexcel 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. data/README +22 -0
  2. data/bin/rubyfromexcel +20 -0
  3. data/examples/create_and_test_examples.rb +37 -0
  4. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet1.rb +59 -0
  5. data/examples/ruby-versions/array-formulas-ruby/sheets/sheet2.rb +9 -0
  6. data/examples/ruby-versions/array-formulas-ruby/specs/sheet1_rspec.rb +156 -0
  7. data/examples/ruby-versions/array-formulas-ruby/specs/sheet2_rspec.rb +8 -0
  8. data/examples/ruby-versions/array-formulas-ruby/spreadsheet.rb +9 -0
  9. data/examples/ruby-versions/complex-test-ruby/sheets/sheet1.rb +305 -0
  10. data/examples/ruby-versions/complex-test-ruby/sheets/sheet2.rb +147 -0
  11. data/examples/ruby-versions/complex-test-ruby/specs/sheet1_rspec.rb +876 -0
  12. data/examples/ruby-versions/complex-test-ruby/specs/sheet2_rspec.rb +412 -0
  13. data/examples/ruby-versions/complex-test-ruby/spreadsheet.rb +9 -0
  14. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet1.rb +9 -0
  15. data/examples/ruby-versions/namedReferenceTest-ruby/sheets/sheet2.rb +8 -0
  16. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet1_rspec.rb +16 -0
  17. data/examples/ruby-versions/namedReferenceTest-ruby/specs/sheet2_rspec.rb +16 -0
  18. data/examples/ruby-versions/namedReferenceTest-ruby/spreadsheet.rb +9 -0
  19. data/examples/ruby-versions/pruning-ruby/sheets/sheet1.rb +11 -0
  20. data/examples/ruby-versions/pruning-ruby/sheets/sheet2.rb +14 -0
  21. data/examples/ruby-versions/pruning-ruby/sheets/sheet3.rb +7 -0
  22. data/examples/ruby-versions/pruning-ruby/specs/sheet1_rspec.rb +20 -0
  23. data/examples/ruby-versions/pruning-ruby/specs/sheet2_rspec.rb +20 -0
  24. data/examples/ruby-versions/pruning-ruby/specs/sheet3_rspec.rb +8 -0
  25. data/examples/ruby-versions/pruning-ruby/spreadsheet.rb +9 -0
  26. data/examples/ruby-versions/sharedFormulaTest-ruby/sheets/sheet1.rb +15 -0
  27. data/examples/ruby-versions/sharedFormulaTest-ruby/specs/sheet1_rspec.rb +44 -0
  28. data/examples/ruby-versions/sharedFormulaTest-ruby/spreadsheet.rb +9 -0
  29. data/examples/ruby-versions/table-test-ruby/sheets/sheet1.rb +17 -0
  30. data/examples/ruby-versions/table-test-ruby/sheets/sheet2.rb +5 -0
  31. data/examples/ruby-versions/table-test-ruby/sheets/sheet3.rb +5 -0
  32. data/examples/ruby-versions/table-test-ruby/specs/sheet1_rspec.rb +20 -0
  33. data/examples/ruby-versions/table-test-ruby/specs/sheet2_rspec.rb +8 -0
  34. data/examples/ruby-versions/table-test-ruby/specs/sheet3_rspec.rb +8 -0
  35. data/examples/ruby-versions/table-test-ruby/spreadsheet.rb +9 -0
  36. data/examples/sheets/array-formulas.xlsx +0 -0
  37. data/examples/sheets/complex-test.xlsx +0 -0
  38. data/examples/sheets/namedReferenceTest.xlsx +0 -0
  39. data/examples/sheets/pruning.xlsx +0 -0
  40. data/examples/sheets/sharedFormulaTest.xlsx +0 -0
  41. data/examples/sheets/table-test.xlsx +0 -0
  42. data/examples/sheets/~$array-formulas.xlsx +0 -0
  43. data/examples/unzipped-sheets/array-formulas/[Content_Types].xml +2 -0
  44. data/examples/unzipped-sheets/array-formulas/docProps/app.xml +2 -0
  45. data/examples/unzipped-sheets/array-formulas/docProps/core.xml +2 -0
  46. data/examples/unzipped-sheets/array-formulas/docProps/thumbnail.jpeg +0 -0
  47. data/examples/unzipped-sheets/array-formulas/xl/_rels/workbook.xml.rels +2 -0
  48. data/examples/unzipped-sheets/array-formulas/xl/calcChain.xml +2 -0
  49. data/examples/unzipped-sheets/array-formulas/xl/sharedStrings.xml +2 -0
  50. data/examples/unzipped-sheets/array-formulas/xl/styles.xml +2 -0
  51. data/examples/unzipped-sheets/array-formulas/xl/theme/theme1.xml +2 -0
  52. data/examples/unzipped-sheets/array-formulas/xl/workbook.xml +2 -0
  53. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet1.xml +2 -0
  54. data/examples/unzipped-sheets/array-formulas/xl/worksheets/sheet2.xml +2 -0
  55. data/examples/unzipped-sheets/complex-test/[Content_Types].xml +2 -0
  56. data/examples/unzipped-sheets/complex-test/docProps/app.xml +2 -0
  57. data/examples/unzipped-sheets/complex-test/docProps/core.xml +2 -0
  58. data/examples/unzipped-sheets/complex-test/xl/_rels/workbook.xml.rels +2 -0
  59. data/examples/unzipped-sheets/complex-test/xl/calcChain.xml +2 -0
  60. data/examples/unzipped-sheets/complex-test/xl/charts/chart1.xml +2 -0
  61. data/examples/unzipped-sheets/complex-test/xl/charts/chart2.xml +2 -0
  62. data/examples/unzipped-sheets/complex-test/xl/comments1.xml +5 -0
  63. data/examples/unzipped-sheets/complex-test/xl/comments2.xml +5 -0
  64. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing1.xml.rels +2 -0
  65. data/examples/unzipped-sheets/complex-test/xl/drawings/_rels/drawing2.xml.rels +2 -0
  66. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing1.xml +2 -0
  67. data/examples/unzipped-sheets/complex-test/xl/drawings/drawing2.xml +2 -0
  68. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing1.vml +46 -0
  69. data/examples/unzipped-sheets/complex-test/xl/drawings/vmlDrawing2.vml +46 -0
  70. data/examples/unzipped-sheets/complex-test/xl/sharedStrings.xml +2 -0
  71. data/examples/unzipped-sheets/complex-test/xl/styles.xml +2 -0
  72. data/examples/unzipped-sheets/complex-test/xl/theme/theme1.xml +2 -0
  73. data/examples/unzipped-sheets/complex-test/xl/workbook.xml +2 -0
  74. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  75. data/examples/unzipped-sheets/complex-test/xl/worksheets/_rels/sheet2.xml.rels +2 -0
  76. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet1.xml +2 -0
  77. data/examples/unzipped-sheets/complex-test/xl/worksheets/sheet2.xml +2 -0
  78. data/examples/unzipped-sheets/namedReferenceTest/[Content_Types].xml +2 -0
  79. data/examples/unzipped-sheets/namedReferenceTest/docProps/app.xml +2 -0
  80. data/examples/unzipped-sheets/namedReferenceTest/docProps/core.xml +2 -0
  81. data/examples/unzipped-sheets/namedReferenceTest/docProps/thumbnail.jpeg +0 -0
  82. data/examples/unzipped-sheets/namedReferenceTest/xl/_rels/workbook.xml.rels +2 -0
  83. data/examples/unzipped-sheets/namedReferenceTest/xl/calcChain.xml +2 -0
  84. data/examples/unzipped-sheets/namedReferenceTest/xl/styles.xml +2 -0
  85. data/examples/unzipped-sheets/namedReferenceTest/xl/theme/theme1.xml +2 -0
  86. data/examples/unzipped-sheets/namedReferenceTest/xl/workbook.xml +2 -0
  87. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet1.xml +2 -0
  88. data/examples/unzipped-sheets/namedReferenceTest/xl/worksheets/sheet2.xml +2 -0
  89. data/examples/unzipped-sheets/pruning/[Content_Types].xml +2 -0
  90. data/examples/unzipped-sheets/pruning/docProps/app.xml +2 -0
  91. data/examples/unzipped-sheets/pruning/docProps/core.xml +2 -0
  92. data/examples/unzipped-sheets/pruning/docProps/thumbnail.jpeg +0 -0
  93. data/examples/unzipped-sheets/pruning/xl/_rels/workbook.xml.rels +2 -0
  94. data/examples/unzipped-sheets/pruning/xl/calcChain.xml +2 -0
  95. data/examples/unzipped-sheets/pruning/xl/sharedStrings.xml +2 -0
  96. data/examples/unzipped-sheets/pruning/xl/styles.xml +2 -0
  97. data/examples/unzipped-sheets/pruning/xl/theme/theme1.xml +2 -0
  98. data/examples/unzipped-sheets/pruning/xl/workbook.xml +2 -0
  99. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet1.xml +2 -0
  100. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet2.xml +2 -0
  101. data/examples/unzipped-sheets/pruning/xl/worksheets/sheet3.xml +2 -0
  102. data/examples/unzipped-sheets/sharedFormulaTest/[Content_Types].xml +2 -0
  103. data/examples/unzipped-sheets/sharedFormulaTest/docProps/app.xml +2 -0
  104. data/examples/unzipped-sheets/sharedFormulaTest/docProps/core.xml +2 -0
  105. data/examples/unzipped-sheets/sharedFormulaTest/docProps/thumbnail.jpeg +0 -0
  106. data/examples/unzipped-sheets/sharedFormulaTest/xl/_rels/workbook.xml.rels +2 -0
  107. data/examples/unzipped-sheets/sharedFormulaTest/xl/calcChain.xml +2 -0
  108. data/examples/unzipped-sheets/sharedFormulaTest/xl/styles.xml +2 -0
  109. data/examples/unzipped-sheets/sharedFormulaTest/xl/theme/theme1.xml +2 -0
  110. data/examples/unzipped-sheets/sharedFormulaTest/xl/workbook.xml +2 -0
  111. data/examples/unzipped-sheets/sharedFormulaTest/xl/worksheets/sheet1.xml +2 -0
  112. data/examples/unzipped-sheets/table-test/[Content_Types].xml +2 -0
  113. data/examples/unzipped-sheets/table-test/docProps/app.xml +2 -0
  114. data/examples/unzipped-sheets/table-test/docProps/core.xml +2 -0
  115. data/examples/unzipped-sheets/table-test/xl/_rels/workbook.xml.rels +2 -0
  116. data/examples/unzipped-sheets/table-test/xl/calcChain.xml +2 -0
  117. data/examples/unzipped-sheets/table-test/xl/printerSettings/printerSettings1.bin +0 -0
  118. data/examples/unzipped-sheets/table-test/xl/sharedStrings.xml +2 -0
  119. data/examples/unzipped-sheets/table-test/xl/styles.xml +2 -0
  120. data/examples/unzipped-sheets/table-test/xl/tables/table1.xml +2 -0
  121. data/examples/unzipped-sheets/table-test/xl/theme/theme1.xml +2 -0
  122. data/examples/unzipped-sheets/table-test/xl/workbook.xml +2 -0
  123. data/examples/unzipped-sheets/table-test/xl/worksheets/_rels/sheet1.xml.rels +2 -0
  124. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet1.xml +2 -0
  125. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet2.xml +2 -0
  126. data/examples/unzipped-sheets/table-test/xl/worksheets/sheet3.xml +2 -0
  127. data/lib/cells/array/array_formula_builder.rb +58 -0
  128. data/lib/cells/array/array_formula_cell.rb +27 -0
  129. data/lib/cells/array/arraying_formula_cell.rb +67 -0
  130. data/lib/cells/array/single_cell_array_formula_builder.rb +9 -0
  131. data/lib/cells/array/single_cell_array_formula_cell.rb +11 -0
  132. data/lib/cells/cell.rb +98 -0
  133. data/lib/cells/cells.rb +9 -0
  134. data/lib/cells/formula/formula_cell.rb +18 -0
  135. data/lib/cells/formula/simple_formula_cell.rb +4 -0
  136. data/lib/cells/shared/shared_formula_builder.rb +15 -0
  137. data/lib/cells/shared/shared_formula_cell.rb +20 -0
  138. data/lib/cells/shared/sharing_formula_cell.rb +36 -0
  139. data/lib/cells/value/value_cell.rb +24 -0
  140. data/lib/excelfile/excelfile.rb +6 -0
  141. data/lib/excelfile/relationships.rb +24 -0
  142. data/lib/excelfile/shared_strings.rb +21 -0
  143. data/lib/excelfile/sheet_names.rb +6 -0
  144. data/lib/excelfile/table.rb +116 -0
  145. data/lib/excelfile/workbook.rb +108 -0
  146. data/lib/excelfile/worksheet.rb +122 -0
  147. data/lib/formulae/compile/formula_builder.rb +316 -0
  148. data/lib/formulae/formulae.rb +6 -0
  149. data/lib/formulae/parse/formula_peg.rb +213 -0
  150. data/lib/formulae/parse/formula_peg.txt +40 -0
  151. data/lib/formulae/run/excel_functions.rb +375 -0
  152. data/lib/formulae/run/excel_matrix.rb +114 -0
  153. data/lib/formulae/run/excel_range.rb +256 -0
  154. data/lib/formulae/run/reference.rb +79 -0
  155. data/lib/optimiser/dependency_builder.rb +86 -0
  156. data/lib/optimiser/optimiser.rb +3 -0
  157. data/lib/optimiser/shared_formula_dependency_builder.rb +43 -0
  158. data/lib/optimiser/workbook_pruner.rb +80 -0
  159. data/lib/rubyfromexcel.rb +105 -0
  160. data/lib/runtime/runtime_formula_builder.rb +32 -0
  161. data/spec/array_formula_builder_spec.rb +35 -0
  162. data/spec/array_formula_cell_spec.rb +17 -0
  163. data/spec/arraying_formula_cell_spec.rb +38 -0
  164. data/spec/dependency_builder_spec.rb +71 -0
  165. data/spec/excel_functions_spec.rb +381 -0
  166. data/spec/excel_matrix_spec.rb +92 -0
  167. data/spec/excel_range_spec.rb +161 -0
  168. data/spec/formula_builder_spec.rb +230 -0
  169. data/spec/formula_peg_spec.rb +165 -0
  170. data/spec/reference_spec.rb +72 -0
  171. data/spec/relationships_spec.rb +51 -0
  172. data/spec/runtime_formula_builder_spec.rb +55 -0
  173. data/spec/shared_formula_builder_spec.rb +29 -0
  174. data/spec/shared_formula_cell_spec.rb +23 -0
  175. data/spec/shared_formula_dependency_builder_spec.rb +48 -0
  176. data/spec/shared_strings_spec.rb +14 -0
  177. data/spec/sharing_formula_cell_spec.rb +79 -0
  178. data/spec/simple_formula_cell_spec.rb +78 -0
  179. data/spec/single_cell_array_formula_builder_spec.rb +19 -0
  180. data/spec/single_cell_array_formula_cell_spec.rb +25 -0
  181. data/spec/spec_helper.rb +2 -0
  182. data/spec/table_spec.rb +100 -0
  183. data/spec/value_cell_spec.rb +49 -0
  184. data/spec/workbook_pruner_spec.rb +27 -0
  185. data/spec/workbook_spec.rb +283 -0
  186. data/spec/worksheet_failiures_spec.rb +41 -0
  187. data/spec/worksheet_spec.rb +486 -0
  188. metadata +291 -0
@@ -0,0 +1,114 @@
1
+ module RubyFromExcel
2
+ class ExcelMatrixCollection
3
+ include Enumerable
4
+
5
+ attr_accessor :matrices, :max_rows, :max_columns
6
+
7
+ def initialize(*args)
8
+ self.matrices = args.map do |a|
9
+ a.respond_to?(:to_excel_matrix) ? a.to_excel_matrix : ExcelMatrix.new(a)
10
+ end
11
+ coerce_to_equal_sizes
12
+ end
13
+
14
+ def coerce_to_equal_sizes
15
+ self.max_rows = matrices.max_by(&:rows).rows
16
+ self.max_columns = matrices.max_by(&:columns).columns
17
+ matrices.each do |m|
18
+ m.add_columns!(max_columns - m.columns)
19
+ m.add_rows!(max_rows - m.rows)
20
+ end
21
+ end
22
+
23
+ def each &block
24
+ block ? to_enum.each { |e| yield e } : to_enum
25
+ end
26
+
27
+ def to_enum
28
+ Enumerator.new do |yielder|
29
+ 0.upto(max_rows-1) do |j|
30
+ 0.upto(max_columns-1) do |i|
31
+ yielder << matrices.map { |m| m.values[j][i] }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def matrix_map
38
+ results = Array.new(max_rows) { Array.new(max_columns) }
39
+ 0.upto(max_rows-1) do |j|
40
+ 0.upto(max_columns-1) do |i|
41
+ results[j][i] = yield *matrices.map { |m| m.values[j][i] }
42
+ end
43
+ end
44
+ em = ExcelMatrix.new(results)
45
+ if em.rows == 1 && em.columns == 1
46
+ return em.values.first.first
47
+ else
48
+ return em
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+
55
+ class ExcelMatrix < ExcelRange
56
+ include Enumerable
57
+
58
+ attr_accessor :values
59
+
60
+ def initialize(values)
61
+ @values = values.is_a?(Array) ? (values.first.is_a?(Array) ? values : [values] ) : [[values]]
62
+ end
63
+
64
+ # origin 0,0
65
+ def array_formula_offset(row_index,column_index)
66
+ row_index = 0 if values.size == 1
67
+ column_index = 0 if values[row_index].size == 1
68
+ values[row_index][column_index] || :na
69
+ end
70
+
71
+ def rows
72
+ return 0 unless values
73
+ values.size
74
+ end
75
+
76
+ def columns
77
+ return 0 unless values && values.first
78
+ values.first.size
79
+ end
80
+
81
+ def add_rows!(number = 1)
82
+ return if number < 1
83
+ number.times { self.values << self.values.first }
84
+ end
85
+
86
+ def add_columns!(number = 1)
87
+ return if number < 1
88
+ number.times do
89
+ values.each do |row|
90
+ row << row.last
91
+ end
92
+ end
93
+ end
94
+
95
+ def each &block
96
+ block ? to_enum.each { |e| yield e } : to_enum
97
+ end
98
+
99
+ def to_enum
100
+ Enumerator.new do |yielder|
101
+ values.each do |row|
102
+ row.each do |value|
103
+ yielder << value
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def to_excel_matrix
110
+ self
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,256 @@
1
+ module RubyFromExcel
2
+
3
+ class ExcelRange
4
+ end
5
+
6
+ class ExcelRangeCommon < ExcelRange
7
+ include Enumerable
8
+ attr_accessor :sheet
9
+
10
+ def each &block
11
+ block ? to_enum.each { |e| yield e } : to_enum
12
+ end
13
+
14
+ private
15
+
16
+ def defined_cells(regexp = /^[a-z]{1,3}\d+$/io)
17
+ sheet.methods.find_all {|m| m =~ regexp }
18
+ end
19
+
20
+ def value_at(reference)
21
+ sheet.send(reference.to_s)
22
+ rescue ZeroDivisionError => e
23
+ return :div0
24
+ end
25
+ end
26
+
27
+ class Area < ExcelRangeCommon
28
+ attr_accessor :start_cell, :end_cell
29
+
30
+ def initialize(sheet, start_cell,end_cell)
31
+ self.sheet = sheet
32
+ self.start_cell = Reference.new(start_cell)
33
+ self.end_cell = Reference.new(end_cell)
34
+ end
35
+
36
+ def row(index) # index = 0 for top row
37
+ row_number = row_number_for_index(index)
38
+ Area.new(sheet,"#{start_cell.column}#{row_number}","#{end_cell.column}#{row_number}")
39
+ end
40
+
41
+ def rows(start_index, end_index)
42
+ start_row_number = row_number_for_index(start_index)
43
+ end_row_number = row_number_for_index(end_index)
44
+ Area.new(sheet,"#{start_cell.column}#{start_row_number}","#{end_cell.column}#{end_row_number}")
45
+ end
46
+
47
+ def column(index) # index = 0 for first column
48
+ column_number = start_cell.column_number + index
49
+ Area.new(sheet,Reference.ruby_for(column_number,start_cell.row_number),Reference.ruby_for(column_number,end_cell.row_number))
50
+ end
51
+
52
+ def index(row_number,column_number) #origin at 1,1
53
+ reference = index_reference(row_number,column_number)
54
+ return reference if reference.is_a?(Symbol)
55
+ value_at(reference.to_ruby)
56
+ end
57
+
58
+ def index_reference(row_number,column_number)
59
+ ref = array_formula_index(row_number-1, column_number-1)
60
+ return ref if ref.is_a?(Symbol)
61
+ ref.worksheet = sheet
62
+ ref
63
+ end
64
+
65
+ def array_formula_offset(row_index,column_index)
66
+ value_at array_formula_index(row_index,column_index).to_ruby
67
+ end
68
+
69
+ def array_formula_index(row_index,column_index)
70
+ column_index = 0 if single_column?
71
+ row_index = 0 if single_row?
72
+ reference = start_cell.shift([row_index,column_index])
73
+ return :ref unless include?(reference)
74
+ reference
75
+ end
76
+
77
+ def each(&block)
78
+ return @array.each(&block) if @array
79
+ start_cell.row_number.upto(end_cell.row_number) do |row|
80
+ start_cell.column_number.upto(end_cell.column_number) do |column_number|
81
+ yield value_at(Reference.ruby_for(column_number,row))
82
+ end
83
+ end
84
+ end
85
+
86
+ # def to_enum
87
+ # to_reference_enum.map {|reference| value_at reference}
88
+ # end
89
+
90
+ def to_a
91
+ @array ||= self.map {|v| v }
92
+ end
93
+
94
+ def dependencies
95
+ @dependencies ||=
96
+ (start_cell.row_number..end_cell.row_number).map do |row|
97
+ (start_cell.column_number..end_cell.column_number).map do |column_number|
98
+ Reference.ruby_for(column_number,row,sheet)
99
+ end
100
+ end.flatten
101
+ end
102
+
103
+ def to_reference_enum
104
+ Enumerator.new do |yielder|
105
+ start_cell.row_number.upto(end_cell.row_number) do |row|
106
+ start_cell.column_number.upto(end_cell.column_number) do |column_number|
107
+ yielder << Reference.ruby_for(column_number,row)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def to_excel_matrix
114
+ ExcelMatrix.new(to_grid)
115
+ end
116
+
117
+ def to_grid
118
+ (start_cell.row_number..end_cell.row_number).map do |row|
119
+ (start_cell.column_number..end_cell.column_number).map do |column_number|
120
+ value_at(Reference.ruby_for(column_number,row))
121
+ end
122
+ end
123
+ end
124
+
125
+ def single_row_or_column?
126
+ single_row? || single_column?
127
+ end
128
+
129
+ def single_column?
130
+ start_cell.column == end_cell.column
131
+ end
132
+
133
+ def single_row?
134
+ start_cell.row_number == end_cell.row_number
135
+ end
136
+
137
+ def to_s
138
+ "#{sheet}.a('#{start_cell.to_ruby}','#{end_cell.to_ruby}')"
139
+ end
140
+
141
+ def include_cell?(cell)
142
+ return false unless cell.worksheet == sheet
143
+ include?(cell.reference)
144
+ end
145
+
146
+ def row_number_for_index(index)
147
+ return (end_cell.row_number + 1 + index) if index < 0
148
+ row_number = start_cell.row_number + index
149
+ end
150
+
151
+ def include?(reference)
152
+ return false unless reference.column_number >= start_cell.column_number
153
+ return false unless reference.column_number <= end_cell.column_number
154
+ return false unless reference.row_number >= start_cell.row_number
155
+ return false unless reference.row_number <= end_cell.row_number
156
+ true
157
+ end
158
+
159
+ def method_missing(method,*args,&block)
160
+ super unless start_cell.to_s == end_cell.to_s
161
+ super unless value_at(start_cell).respond_to?(method)
162
+ value_at(start_cell).send(method,*args,&block)
163
+ end
164
+
165
+ end
166
+
167
+ class Columns < ExcelRangeCommon
168
+
169
+ attr_accessor :start_column_number, :end_column_number
170
+
171
+ def initialize(sheet,start_column,end_column)
172
+ self.sheet = sheet
173
+ self.start_column_number = Reference.column_to_integer(start_column)
174
+ self.end_column_number = Reference.column_to_integer(end_column)
175
+ end
176
+
177
+ def array_formula_index(row_index,column_index)
178
+ column_index = 0 if single_column?
179
+ reference = Reference.new(Reference.ruby_for(start_column_number+column_index,row_index+1))
180
+ return :na unless include?(reference)
181
+ reference.to_ruby
182
+ end
183
+
184
+ def to_enum
185
+ Enumerator.new do |yielder|
186
+ (start_column_number..end_column_number).each do |column_number|
187
+ (1..maximum_row_in_column_number(column_number)).each do |row|
188
+ yielder << value_at(Reference.ruby_for(column_number,row))
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def include?(reference)
197
+ return false unless reference.column_number >= start_column_number
198
+ return false unless reference.column_number <= end_column_number
199
+ true
200
+ end
201
+
202
+ def maximum_row_in_column_number(column_number)
203
+ cells_in_column = defined_cells(/^#{Reference.integer_to_column(column_number)}\d+$/i)
204
+ cells_in_column.empty? ? 0 : cells_in_column.last[/\d+/i].to_i
205
+ end
206
+
207
+ def single_column?
208
+ start_column_number == end_column_number
209
+ end
210
+ end
211
+
212
+ class Rows < ExcelRangeCommon
213
+ attr_accessor :start_row_number, :end_row_number
214
+
215
+ def initialize(sheet,start_row_number,end_row_number)
216
+ self.sheet = sheet
217
+ self.start_row_number = start_row_number.to_i
218
+ self.end_row_number = end_row_number.to_i
219
+ end
220
+
221
+ def array_formula_index(row_index,column_index)
222
+ row_index = 0 if single_row?
223
+ reference = Reference.new(Reference.ruby_for(column_index+1,row_index+1))
224
+ return :na unless include?(reference)
225
+ reference.to_ruby
226
+ end
227
+
228
+ def to_enum
229
+ Enumerator.new do |yielder|
230
+ (start_row_number..end_row_number).each do |row|
231
+ (1..maximum_column_number_in_row_number(row)).each do |column_number|
232
+ yielder << value_at(Reference.ruby_for(column_number,row))
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ def maximum_column_number_in_row_number(row_number)
241
+ cells_in_row = defined_cells(/^[a-z]{1,3}#{row_number}$/i)
242
+ cells_in_row.empty? ? 0 : Reference.column_to_integer(cells_in_row.last[/[a-z]+/i])
243
+ end
244
+
245
+ def single_row?
246
+ start_row_number == end_row_number
247
+ end
248
+
249
+ def include?(reference)
250
+ return false unless reference.row_number >= start_row_number
251
+ return false unless reference.row_number <= end_row_number
252
+ true
253
+ end
254
+
255
+ end
256
+ end
@@ -0,0 +1,79 @@
1
+ module RubyFromExcel
2
+ class Reference
3
+ attr_accessor :row, :column, :worksheet
4
+ attr_accessor :absolute_row, :absolute_column
5
+
6
+ def self.ruby_for(column_number,row_number,worksheet = nil)
7
+ worksheet ? "#{worksheet}.#{integer_to_column(column_number)}#{row_number.to_i}" : "#{integer_to_column(column_number)}#{row_number.to_i}"
8
+ end
9
+
10
+ def self.column_to_integer(string)
11
+ @column_to_integer_cache ||= {}
12
+ return @column_to_integer_cache[string] ||= string.downcase.each_byte.to_a.reverse.each.with_index.inject(0) do |memo,byte_with_index|
13
+ memo = memo + ((byte_with_index.first - 96) * (26**byte_with_index.last))
14
+ end
15
+ end
16
+
17
+ def self.integer_to_column(integer)
18
+ @integer_to_column_cache ||= {}
19
+ return @integer_to_column_cache[integer] ||= self.calculate_integer_to_column(integer)
20
+ end
21
+
22
+ def self.calculate_integer_to_column(integer)
23
+ raise Exception.new("Column numbering starts at 1, received #{integer}") unless integer >= 1
24
+ text = (integer-1).to_i.to_s(26)
25
+ (text[0...-1].tr('1-9a-z','abcdefghijklmnopqrstuvwxyz')+text[-1,1].tr('0-9a-z','abcdefghijklmnopqrstuvwxyz')).gsub('a0','z').gsub(/([b-z])0/) { $1.tr('b-z','a-y')+"z" }
26
+ end
27
+
28
+ def initialize(reference_as_text, worksheet = nil)
29
+ @worksheet = worksheet
30
+ reference_as_text.downcase!
31
+ @row = reference_as_text[/\d+/]
32
+ @column = reference_as_text[/[a-z]+/]
33
+ @absolute_row = (reference_as_text =~ /\$\d/)
34
+ @absolute_column = (reference_as_text =~ /\$[a-z]/)
35
+ end
36
+
37
+ def shift(offset_array)
38
+ self.dup.shift!(offset_array)
39
+ end
40
+
41
+ def shift!(offset_array)
42
+ return self unless offset_array && offset_array.size == 2
43
+ shift_row_by! offset_array.first
44
+ shift_column_by! offset_array.last
45
+ self
46
+ end
47
+
48
+ def to_ruby(include_worksheet = false)
49
+ include_worksheet ? "#{worksheet}.#{column}#{row && row.to_i}".downcase : "#{column}#{row && row.to_i}".downcase
50
+ end
51
+
52
+ alias to_s to_ruby
53
+
54
+ def row_number
55
+ row.to_i
56
+ end
57
+
58
+ def column_number
59
+ Reference.column_to_integer(column)
60
+ end
61
+
62
+ def -(other_reference)
63
+ [row_number - other_reference.row_number, column_number - other_reference.column_number]
64
+ end
65
+
66
+ private
67
+
68
+ def shift_row_by!(offset)
69
+ return if absolute_row
70
+ self.row = (row_number + offset).to_s
71
+ end
72
+
73
+ def shift_column_by!(offset)
74
+ return if absolute_column
75
+ self.column = Reference.integer_to_column(column_number + offset)
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,86 @@
1
+ module RubyFromExcel
2
+ class DependencyBuilder
3
+ attr_accessor :formula_cell, :dependencies, :worksheet
4
+
5
+ def initialize(formula_cell = nil)
6
+ self.formula_cell = formula_cell
7
+ self.worksheet = formula_cell.worksheet
8
+ end
9
+
10
+ def formula(*expressions)
11
+ self.dependencies = []
12
+ expressions.each do |e|
13
+ e.visit(self)
14
+ end
15
+ self.dependencies.uniq.sort
16
+ end
17
+
18
+ def sheet_reference(sheet_name,reference)
19
+ sheet_name = $1 if sheet_name.to_s =~ /^(\d+)\.0+$/
20
+ using_worksheet(SheetNames.instance[sheet_name]) do
21
+ reference.visit(self)
22
+ end
23
+ end
24
+
25
+ alias quoted_sheet_reference sheet_reference
26
+
27
+ def named_reference(name)
28
+ dependencies_for reference_for_name(name)
29
+ end
30
+
31
+ def dependencies_for(full_reference)
32
+ return [] unless full_reference =~ /(sheet\d+)\.(.*)/
33
+ sheet_name, reference = $1, $2
34
+ using_worksheet(sheet_name) do
35
+ case reference
36
+ when /a\('(.*?)','(.*?)'\)/; area($1,$2)
37
+ else; self.dependencies << full_reference
38
+ end
39
+ end
40
+ end
41
+
42
+ def table_reference(table_name,structured_reference)
43
+ dependencies_for Table.reference_for(table_name,structured_reference,formula_cell && formula_cell.reference).to_s
44
+ end
45
+
46
+ def local_table_reference(structured_reference)
47
+ dependencies_for Table.reference_for_local_reference(formula_cell,structured_reference).to_s
48
+ end
49
+
50
+ def cell(reference)
51
+ self.dependencies << Reference.new(reference,worksheet).to_ruby(true)
52
+ Reference.new(reference).to_ruby
53
+ end
54
+
55
+ def area(start_area,end_area)
56
+ self.dependencies.concat Area.new(worksheet,Reference.new(start_area).to_ruby,Reference.new(end_area).to_ruby).dependencies
57
+ end
58
+
59
+ def function(name,*args)
60
+ if name == "INDIRECT"
61
+ args.first.visit(self)
62
+ dependencies_for FormulaBuilder.new(formula_cell).indirect_function(args.first)
63
+ else
64
+ args.each { |a| a.visit(self) }
65
+ end
66
+ end
67
+
68
+ def reference_for_name(name)
69
+ worksheet.named_references[name.to_method_name] ||
70
+ workbook.named_references[name.to_method_name] ||
71
+ (raise Exception.new("#{name} not found"))
72
+ end
73
+
74
+ def workbook
75
+ formula_cell.worksheet.workbook
76
+ end
77
+
78
+ def using_worksheet(sheet_name)
79
+ original_worksheet = self.worksheet
80
+ self.worksheet = workbook.worksheets[sheet_name] || (raise Exception.new("#{sheet_name} not found"))
81
+ yield
82
+ self.worksheet = original_worksheet
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'dependency_builder'
2
+ require_relative 'shared_formula_dependency_builder'
3
+ require_relative 'workbook_pruner'
@@ -0,0 +1,43 @@
1
+ module RubyFromExcel
2
+
3
+ class SharedFormulaDependencyBuilder < DependencyBuilder
4
+ attr_accessor :shared_formula_offset
5
+
6
+ def initialize(formula_cell = nil, shared_formula_offset = nil)
7
+ super formula_cell
8
+ self.shared_formula_offset = shared_formula_offset
9
+ end
10
+
11
+ def cell(reference)
12
+ self.dependencies << Reference.new(reference,worksheet).shift!(shared_formula_offset).to_ruby(true)
13
+ Reference.new(reference).to_ruby
14
+ end
15
+
16
+ def function(name,*args)
17
+ if name == "INDIRECT"
18
+ args.first.visit(self)
19
+ dependencies_for SharedFormulaBuilder.new(formula_cell,shared_formula_offset).indirect_function(args.first)
20
+ else
21
+ args.each { |a| a.visit(self) }
22
+ end
23
+ end
24
+
25
+ alias area_without_offset area
26
+
27
+ def area(start_area,end_area)
28
+ self.dependencies.concat Area.new(worksheet,Reference.new(start_area).shift!(shared_formula_offset).to_ruby,Reference.new(end_area).shift!(shared_formula_offset).to_ruby).dependencies
29
+ end
30
+
31
+ def dependencies_for(full_reference)
32
+ return [] unless full_reference =~ /(sheet\d+)\.(.*)/
33
+ sheet_name, reference = $1, $2
34
+ using_worksheet(sheet_name) do
35
+ case reference
36
+ when /a\('(.*?)','(.*?)'\)/; area_without_offset($1,$2)
37
+ else; self.dependencies << full_reference
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,80 @@
1
+ module RubyFromExcel
2
+
3
+ class WorkbookPruner
4
+ attr_reader :workbook, :cells_to_keep, :output_sheet_names
5
+
6
+ def initialize(workbook)
7
+ @workbook = workbook
8
+ end
9
+
10
+ def prune_cells_not_needed_for_output_sheets(*output_sheet_names)
11
+ @cells_to_keep = {}
12
+ workbook.work_out_dependencies
13
+ @output_sheet_names = output_sheet_names
14
+ find_dependencies_of output_sheet_names
15
+ delete_cells_that_we_dont_want_to_keep
16
+ cells_to_keep = nil # So that we can garbage collect
17
+ GC.start
18
+ end
19
+
20
+ def find_dependencies_of(output_sheet_names)
21
+ output_sheet_names.each do |output_sheet_name|
22
+ puts "Cascading dependencies for #{output_sheet_name}"
23
+ output_sheet = workbook.worksheets[SheetNames.instance[output_sheet_name]]
24
+ raise Exception.new("#{output_sheet_name} not found #{SheetNames.instance.inspect} #{workbook.worksheets.keys.inspect}") unless output_sheet
25
+ output_sheet.cells.each do |reference,cell|
26
+ keep_dependencies_for(cell)
27
+ end
28
+ end
29
+ puts "#{cells_to_keep.size} cells kept, #{workbook.total_cells - cells_to_keep.size} pruned"
30
+ end
31
+
32
+ def keep_dependencies_for(cell)
33
+ return unless cell
34
+ return if cells_to_keep.has_key?(cell.to_s)
35
+ cells_to_keep[cell.to_s] = cell
36
+ cell.dependencies.each do |reference|
37
+ keep_dependencies_for workbook.cell(reference)
38
+ end
39
+ end
40
+
41
+ def delete_cells_that_we_dont_want_to_keep
42
+ workbook.worksheets.each do |name,sheet|
43
+ sheet.cells.delete_if do |reference,cell|
44
+ !cells_to_keep.has_key?(cell.to_s)
45
+ end
46
+ end
47
+ end
48
+
49
+ def convert_cells_to_values_when_independent_of_input_sheets(*input_sheet_names)
50
+ input_sheet_names = input_sheet_names.map { |name| SheetNames.instance[name] }
51
+ count = 0
52
+ workbook.worksheets.each do |name,sheet|
53
+ puts "Converting cells into values in #{name}"
54
+ sheet.cells.each do |reference,cell|
55
+ next if cell.is_a?(ValueCell)
56
+ unless depends_on_input_sheets?(cell,input_sheet_names)
57
+ # puts "converting #{cell}"
58
+ count = count + 1
59
+ sheet.replace_cell(reference,ValueCell.for(cell))
60
+ end
61
+ end
62
+ end
63
+ puts "#{count} formula cells replaced with their values"
64
+ GC.start
65
+ prune_cells_not_needed_for_output_sheets(*output_sheet_names)
66
+ end
67
+
68
+ def depends_on_input_sheets?(cell,input_sheet_names,stack_level = 0)
69
+ return true if stack_level > 100
70
+ return false unless cell
71
+ return true if input_sheet_names.include?(cell.worksheet.variable_name)
72
+ return false unless cell.dependencies
73
+ return true if cell.dependencies.any? do |reference|
74
+ depends_on_input_sheets?(workbook.cell(reference),input_sheet_names,stack_level + 1)
75
+ end
76
+ return false
77
+ end
78
+
79
+ end
80
+ end