rubyfromexcel 0.0.4

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 (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