excel_templating 0.3.2

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.hound.yml +261 -0
  6. data/.rubocop.ph.yml +44 -0
  7. data/.rubocop.yml +3 -0
  8. data/.yardopts +1 -0
  9. data/ChangeLog.md +8 -0
  10. data/Gemfile +10 -0
  11. data/LICENSE.txt +3 -0
  12. data/README.md +133 -0
  13. data/Rakefile +43 -0
  14. data/excel_templating.gemspec +32 -0
  15. data/lib/excel_templating/document/data_source_registry/registry_list.rb +48 -0
  16. data/lib/excel_templating/document/data_source_registry/registry_renderer.rb +74 -0
  17. data/lib/excel_templating/document/data_source_registry.rb +64 -0
  18. data/lib/excel_templating/document/sheet/repeated_row.rb +39 -0
  19. data/lib/excel_templating/document/sheet.rb +133 -0
  20. data/lib/excel_templating/document.rb +71 -0
  21. data/lib/excel_templating/document_dsl.rb +85 -0
  22. data/lib/excel_templating/excel_abstraction/active_cell_reference.rb +59 -0
  23. data/lib/excel_templating/excel_abstraction/cell.rb +23 -0
  24. data/lib/excel_templating/excel_abstraction/cell_range.rb +26 -0
  25. data/lib/excel_templating/excel_abstraction/cell_reference.rb +39 -0
  26. data/lib/excel_templating/excel_abstraction/date.rb +36 -0
  27. data/lib/excel_templating/excel_abstraction/row.rb +29 -0
  28. data/lib/excel_templating/excel_abstraction/sheet.rb +102 -0
  29. data/lib/excel_templating/excel_abstraction/spread_sheet.rb +28 -0
  30. data/lib/excel_templating/excel_abstraction/time.rb +42 -0
  31. data/lib/excel_templating/excel_abstraction/work_book.rb +47 -0
  32. data/lib/excel_templating/excel_abstraction.rb +16 -0
  33. data/lib/excel_templating/render_helper.rb +14 -0
  34. data/lib/excel_templating/renderer.rb +251 -0
  35. data/lib/excel_templating/rspec_excel_matcher.rb +129 -0
  36. data/lib/excel_templating/version.rb +4 -0
  37. data/lib/excel_templating.rb +4 -0
  38. data/spec/assets/alphalist_7_4.mustache.xlsx +0 -0
  39. data/spec/assets/alphalist_seven_four_expected.xlsx +0 -0
  40. data/spec/assets/valid_cell.mustache.xlsx +0 -0
  41. data/spec/assets/valid_cell_expected.xlsx +0 -0
  42. data/spec/assets/valid_cell_expected_inline.xlsx +0 -0
  43. data/spec/assets/valid_column_expected.xlsx +0 -0
  44. data/spec/cell_validation_spec.rb +114 -0
  45. data/spec/column_validation_spec.rb +47 -0
  46. data/spec/excel_abstraction/active_cell_reference_spec.rb +73 -0
  47. data/spec/excel_abstraction/cell_range_spec.rb +36 -0
  48. data/spec/excel_abstraction/cell_reference_spec.rb +69 -0
  49. data/spec/excel_abstraction/cell_spec.rb +54 -0
  50. data/spec/excel_abstraction/date_spec.rb +27 -0
  51. data/spec/excel_abstraction/row_spec.rb +42 -0
  52. data/spec/excel_abstraction/sheet_spec.rb +83 -0
  53. data/spec/excel_abstraction/spread_sheet_spec.rb +35 -0
  54. data/spec/excel_abstraction/time_spec.rb +27 -0
  55. data/spec/excel_abstraction/work_book_spec.rb +22 -0
  56. data/spec/excel_helper.rb +16 -0
  57. data/spec/excel_templating_spec.rb +141 -0
  58. data/spec/spec_helper.rb +13 -0
  59. metadata +281 -0
@@ -0,0 +1,102 @@
1
+ module ExcelAbstraction
2
+ class Sheet < SimpleDelegator
3
+ attr_reader :active_cell_reference
4
+
5
+ def initialize(sheet, workbook)
6
+ super(sheet)
7
+ @workbook = workbook
8
+ @active_cell_reference = ExcelAbstraction::ActiveCellReference.new
9
+ end
10
+
11
+ def header(value, options = {})
12
+ cell(value, {bold: 1}.merge(options))
13
+ self
14
+ end
15
+
16
+ def headers(array, options = {})
17
+ Array(array).each { |element| header(element, options) }
18
+ self
19
+ end
20
+
21
+ # Fills the cell at the current pointer with the value and format or options specified
22
+ # @param [Object] value Value to place in the cell.
23
+ # @param [Object] options (optional) options to create a format object
24
+ # @param [Object] format (optional) The format to use when creating this cell
25
+ def cell(value, format: nil, type: :auto, **options)
26
+ format = format || _format(options)
27
+ value = Float(value) if value.is_a?(ExcelAbstraction::Time) || value.is_a?(ExcelAbstraction::Date)
28
+
29
+ writer = case type
30
+ when :string then method(:write_string)
31
+ else method(:write)
32
+ end
33
+
34
+ writer.call(active_cell_reference.row, active_cell_reference.col, value, format)
35
+
36
+ if options[:new_row]
37
+ next_row
38
+ else
39
+ active_cell_reference.right
40
+ end
41
+ self
42
+ end
43
+
44
+ # Advance the pointer to the next row.
45
+ # @return nil
46
+ def next_row
47
+ active_cell_reference.newline
48
+ end
49
+
50
+ def cells(array, options = {})
51
+ Array(array).each { |value| cell(value, options) }
52
+ self
53
+ end
54
+
55
+ def merge(length, value, options = {})
56
+ format = _format(options)
57
+ merge_range(active_cell_reference.row, active_cell_reference.col, active_cell_reference.row, active_cell_reference.col + length, value, format)
58
+ active_cell_reference.right(length + 1)
59
+ self
60
+ end
61
+
62
+ def style_row(row, properties = {})
63
+ height = properties[:height]
64
+ format = _format(properties[:format] || {})
65
+ hidden = properties[:hidden] || 0
66
+ outline_level = properties[:level] || 0
67
+ if outline_level
68
+ raise "Outline level can only be between 0 and 7" unless (0..7) === outline_level
69
+ end
70
+ collapse = properties[:collapse] || 0
71
+ set_row(row, height, format, hidden, outline_level, collapse)
72
+ self
73
+ end
74
+
75
+ # Style the numbered column
76
+ # @param [Integer] col 0 based index of the column.
77
+ # @param [Integer] width numeric width of the column (30 = 2.6 inches)
78
+ # @param [Hash] format Properties to set for the format of the column
79
+ # @param [Integer] level Outline level
80
+ # @param [Integer] collapse 1 = collapse, 0 = do not collapse
81
+ def style_col(col, width: nil, format: {}, level: 0, collapse: 0, hidden: nil)
82
+ format = _format(format || {})
83
+ if level
84
+ raise "Outline level can only be between 0 and 7" unless (0..7) === level
85
+ end
86
+ set_column(col, col, width, format, hidden, level, collapse)
87
+ self
88
+ end
89
+
90
+ private
91
+
92
+ def _format(options)
93
+ format = @workbook.add_format
94
+ format.set_align('valign')
95
+ options.each do |key, value|
96
+ method = "set_#{key.to_s}".to_sym
97
+ format.send(method, value) if format.respond_to?(method)
98
+ end
99
+ format
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,28 @@
1
+ require 'tempfile'
2
+
3
+ module ExcelAbstraction
4
+ class SpreadSheet < DelegateClass(Tempfile)
5
+ attr_reader :workbook
6
+
7
+ def initialize(format: :xls, skip_default_sheet: false)
8
+ extension = format == :xls ? ".xls" : ".xlsx"
9
+ tmp_file = Tempfile.new(["temp_spreadsheet_#{::Time.now.to_i}", extension])
10
+ super(tmp_file)
11
+ @workbook = ExcelAbstraction::WorkBook.new(tmp_file.path, format: format, skip_default_sheet: skip_default_sheet)
12
+ end
13
+
14
+ def close
15
+ workbook.close
16
+ yield if block_given?
17
+ super
18
+ end
19
+
20
+ def to_s
21
+ data = nil
22
+ close do
23
+ data = read
24
+ end
25
+ data
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ module ExcelAbstraction
2
+ class Time < SimpleDelegator
3
+ ADJUSTMENT = "1900-03-01 00:00 +00:00"
4
+ REFERENCE = "1900-01-01 00:00 +00:00"
5
+
6
+ attr_reader :value
7
+
8
+ def initialize(raw_value)
9
+ super(convert(raw_value))
10
+ end
11
+
12
+ def to_excel_time
13
+ self
14
+ end
15
+
16
+ private
17
+
18
+ def adjustment
19
+ ::Time.parse(ADJUSTMENT)
20
+ end
21
+
22
+ def reference
23
+ ::Time.parse(REFERENCE)
24
+ end
25
+
26
+ def adjust(raw_value)
27
+ adjustment < raw_value ? two_days : one_day
28
+ end
29
+
30
+ def two_days
31
+ one_day * 2.0
32
+ end
33
+
34
+ def one_day
35
+ 60 * 60 * 24.to_f
36
+ end
37
+
38
+ def convert(raw_value)
39
+ (raw_value - reference + adjust(raw_value)).to_f / one_day
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module ExcelAbstraction
2
+ class WorkBook < SimpleDelegator
3
+ attr_accessor :active_sheet
4
+
5
+ def initialize(file, format: :xls, skip_default_sheet: false)
6
+ @format = format
7
+ @file = file
8
+ super(workbook)
9
+ unless skip_default_sheet
10
+ @active_sheet = ExcelAbstraction::Sheet.new(workbook.add_worksheet, workbook)
11
+ end
12
+ end
13
+
14
+
15
+ def title(text)
16
+ set_properties(title: text)
17
+ self
18
+ end
19
+
20
+ def organization(name)
21
+ set_properties(company: name)
22
+ self
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :format, :file
28
+
29
+ def default_options
30
+ {
31
+ :font => 'Calibri',
32
+ :size => 12,
33
+ :align => 'center',
34
+ :text_wrap => 1
35
+ }
36
+ end
37
+
38
+ def workbook
39
+ @workbook ||= if format == :xlsx
40
+ WriteXLSX.new(file, default_options)
41
+ else
42
+ WriteExcel.new(file, default_options)
43
+ end
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,16 @@
1
+ require 'write_xlsx'
2
+ require 'writeexcel'
3
+
4
+ require_relative 'excel_abstraction/active_cell_reference'
5
+ require_relative 'excel_abstraction/cell'
6
+ require_relative 'excel_abstraction/cell_range'
7
+ require_relative 'excel_abstraction/cell_reference'
8
+ require_relative 'excel_abstraction/date'
9
+ require_relative 'excel_abstraction/row'
10
+ require_relative 'excel_abstraction/sheet'
11
+ require_relative 'excel_abstraction/spread_sheet'
12
+ require_relative 'excel_abstraction/time'
13
+ require_relative 'excel_abstraction/work_book'
14
+
15
+ module ExcelAbstraction
16
+ end
@@ -0,0 +1,14 @@
1
+ module ExcelTemplating
2
+ module RenderHelper
3
+ class << self
4
+ def letter_for(one_based_index)
5
+ letters[one_based_index-1]
6
+ end
7
+
8
+ private
9
+ def letters
10
+ @row_letters ||= ('A' .. 'ZZ').to_a
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,251 @@
1
+ require_relative 'excel_abstraction'
2
+ require 'mustache'
3
+
4
+ module ExcelTemplating
5
+ # Render class for ExcelTemplating Documents. Used by the Document to render the defined document
6
+ # with the data to a new file. Responsible for reading the template and applying the data to it
7
+ class Renderer
8
+ extend Forwardable
9
+
10
+ # @param [ExcelTemplating::Document] document Document to render with
11
+ def initialize(document)
12
+ @template_document = document
13
+ @data_source_registry = document.class.data_source_registry
14
+ end
15
+
16
+ # Render the document provided. Yields the path to the tempfile created.
17
+ def render
18
+ @spreadsheet = ExcelAbstraction::SpreadSheet.new(format: :xlsx)
19
+ @template = Roo::Spreadsheet.open(template_path)
20
+ @registry_renderer = data_source_registry.renderer(data: data[:all_sheets])
21
+ apply_document_level_items
22
+ apply_data_to_sheets
23
+ protect_spreadsheet
24
+ registry_renderer.write_sheet(@spreadsheet.workbook)
25
+
26
+ @spreadsheet.close
27
+ yield(spreadsheet.path)
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :template_document, :spreadsheet, :template, :registry_renderer, :data_source_registry, :current_sheet
33
+ delegate [:workbook] => :spreadsheet
34
+ delegate [:data] => :template_document
35
+ delegate [:active_sheet] => :workbook
36
+ delegate [:active_cell_reference] => :active_sheet
37
+
38
+ def current_row
39
+ active_cell_reference.row
40
+ end
41
+
42
+ def current_col
43
+ active_cell_reference.col
44
+ end
45
+
46
+ def template_path
47
+ template_document.class.template_path
48
+ end
49
+
50
+ def default_format_styling
51
+ template_document.class.document_default_styling
52
+ end
53
+
54
+ def protected?
55
+ template_document.class.protected?
56
+ end
57
+
58
+ def sheets
59
+ template_document.class.sheets
60
+ end
61
+
62
+ def apply_document_level_items
63
+ workbook.title mustachify(template_document.class.document_title, locals: common_data_variables)
64
+ workbook.organization mustachify(template_document.class.document_organization, locals: common_data_variables)
65
+ end
66
+
67
+ def common_data_variables
68
+ stringify_keys(data[:all_sheets])
69
+ end
70
+
71
+ def stringify_keys(hash)
72
+ Hash[hash.map { |k, v| [k.to_s, v] } ]
73
+ end
74
+
75
+ def style_columns(sheet, template_sheet)
76
+ default_style = sheet.default_column_style
77
+ column_styles = sheet.column_styles
78
+ if column_styles || default_style
79
+ roo_columns(template_sheet).each do |column_number|
80
+ style = column_styles[column_number] || default_style
81
+ active_sheet.style_col(column_number - 1, style) # Note: Styling columns is zero indexed
82
+ end
83
+ end
84
+ end
85
+
86
+ def style_rows(sheet, template_sheet)
87
+ default_style = sheet.default_row_style
88
+ row_styles = sheet.row_styles
89
+ if row_styles || default_style
90
+ roo_rows(template_sheet).each do |row_number|
91
+ style = row_styles[row_number] || default_style
92
+ active_sheet.style_row(row_number - 1, style) # Note: Styling rows is zero indexed
93
+ end
94
+ end
95
+ end
96
+
97
+ def roo_columns(roo_sheet)
98
+ (roo_sheet.first_column .. roo_sheet.last_column)
99
+ end
100
+
101
+ def roo_rows(roo_sheet)
102
+ (roo_sheet.first_row .. roo_sheet.last_row)
103
+ end
104
+
105
+ def protect_spreadsheet
106
+ active_sheet.protect if protected?
107
+ end
108
+
109
+ def apply_data_to_sheets
110
+ sheets.each_with_index do |sheet, sheet_number|
111
+ @current_sheet = sheet
112
+ sheet_data = sheet.sheet_data(data)
113
+ template_sheet = template.sheet(sheet_number)
114
+
115
+ # column and row styles should be applied before writing any data to the sheet
116
+ style_columns(sheet, template_sheet)
117
+ # row styles have priority over column styles
118
+ style_rows(sheet, template_sheet)
119
+
120
+ roo_rows(template_sheet).each do |row_number|
121
+ sheet.each_row_at(row_number, sheet_data) do |row_data|
122
+ local_data = stringify_keys(data[:all_sheets]).merge(stringify_keys(row_data))
123
+ roo_columns(template_sheet).each do |column_number|
124
+ apply_data_to_cell(local_data, template_sheet, row_number, column_number)
125
+ if sheet.validated_cell?(row_number, column_number)
126
+ add_validation(sheet, row_number, column_number)
127
+ end
128
+ end
129
+ active_sheet.next_row
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ def apply_data_to_cell(local_data, template_sheet, row_number, column_number)
136
+ template_cell = template_sheet.cell(row_number, column_number)
137
+ font = template_sheet.font(row_number, column_number)
138
+ format = format_for(font, row_number, column_number)
139
+ value = mustachify(template_cell, locals: local_data)
140
+
141
+ active_sheet.cell(
142
+ value,
143
+ type: type_for_value(value),
144
+ format: format
145
+ )
146
+ end
147
+
148
+ def type_for_value(value)
149
+ looks_like_a_separator?(value) ? :string : :auto
150
+ end
151
+
152
+ def looks_like_a_separator?(value)
153
+ value.is_a?(String) && value =~ /^==/
154
+ end
155
+
156
+ def format_for(font, row_number, column_number)
157
+ format = font_formats(font)
158
+
159
+ set_column_lock(format, column_number) if column_is_locked?(column_number)
160
+ set_row_lock(format, row_number) if row_is_locked?(row_number)
161
+
162
+ format
163
+ end
164
+
165
+ def column_is_locked?(column_number)
166
+ row_or_column_is_locked? current_sheet.column_styles[column_number]
167
+ end
168
+
169
+ def row_is_locked?(row_number)
170
+ row_or_column_is_locked? current_sheet.row_styles[row_number]
171
+ end
172
+
173
+ def row_or_column_is_locked?(row_or_column)
174
+ row_or_column && row_or_column[:format] && row_or_column[:format].has_key?(:locked)
175
+ end
176
+
177
+ def row_or_column_lock_attribute(row_or_column)
178
+ row_or_column && row_or_column[:format] && row_or_column[:format][:locked]
179
+ end
180
+
181
+ def set_row_lock(format, row_number)
182
+ format.set_locked row_or_column_lock_attribute(current_sheet.row_styles[row_number])
183
+ end
184
+
185
+ def set_column_lock(format, column_number)
186
+ format.set_locked row_or_column_lock_attribute(current_sheet.column_styles[column_number])
187
+ end
188
+
189
+ def font_formats(font=nil)
190
+ template_font = font || Roo::Font.new
191
+
192
+ format_details = {
193
+ bold: template_font.bold? ? 1 : 0,
194
+ italic: template_font.italic? ? 1 : 0,
195
+ underline: template_font.underline? ? 1 : 0
196
+ }.merge(default_format_styling)
197
+
198
+ workbook.add_format format_details
199
+ end
200
+
201
+ def mustachify(inline_template, locals: {})
202
+ if whole_cell_template?(inline_template)
203
+ locals.fetch(extract_key_from_template(inline_template))
204
+ elsif no_mustache_found?(inline_template)
205
+ inline_template
206
+ else
207
+ MustacheRenderer.render(inline_template, locals)
208
+ end
209
+ end
210
+
211
+ def no_mustache_found?(value)
212
+ !value.is_a?(String) || !(value.match(/{{.+}}/))
213
+ end
214
+
215
+ def extract_key_from_template(template)
216
+ template[whole_cell_template_matcher, 1]
217
+ end
218
+
219
+ def whole_cell_template_matcher
220
+ /^{{([^{}]+)}}$/
221
+ end
222
+
223
+ def whole_cell_template?(template)
224
+ template =~ whole_cell_template_matcher
225
+ end
226
+
227
+ def add_validation(sheet, row_number, column_number)
228
+ raise ArgumentError, "No :data_sources defined for validation!" unless data_source_registry
229
+ source = sheet.validation_source_name(row_number, column_number)
230
+ #Use current_row and current_col here because row_number and column_number refer to the template
231
+ #sheet and we want to write a reference to the cell we just wrote
232
+ active_sheet.data_validation absolute_reference(current_row + 1, current_col),
233
+ registry_renderer.absolute_reference_for(source)
234
+ end
235
+
236
+ def absolute_reference(row_number, column_number)
237
+ "$#{RenderHelper.letter_for(column_number)}$#{row_number}"
238
+ end
239
+
240
+ class MustacheRenderer < Mustache
241
+
242
+ def self.render(template, locals)
243
+ renderer = new
244
+ renderer.template = template
245
+ renderer.raise_on_context_miss = true
246
+ renderer.render(locals)
247
+ end
248
+
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,129 @@
1
+ require 'roo'
2
+ require "roo-xls"
3
+
4
+ # Matcher for rspec 'match_excel_content'
5
+ # @example
6
+ # expect do
7
+ # subject.render do |path|
8
+ # expect(path).to match_excel_content('spec/assets/spreadsheets/seven_three_expected.xlsx')
9
+ # end
10
+ # end
11
+ RSpec::Matchers.define :match_excel_content do |expected_excel_path|
12
+ match do |excel_path|
13
+ @excel_matcher = RSpec::Matchers::ExcelMatcher.new
14
+ @excel_matcher.expected_roo = Roo::Spreadsheet.open(expected_excel_path)
15
+ @excel_matcher.actual_roo = Roo::Spreadsheet.open(excel_path)
16
+ @excel_matcher.match?
17
+ end
18
+
19
+ failure_message do |excel_path|
20
+ messages = ["expected excel:#{expected_excel_path} to exactly match the content of #{excel_path} but did not."]
21
+ @excel_matcher.errors.group_by(&:first).each do |sheet_name, errors|
22
+ messages << "#{sheet_name}:"
23
+ errors.each do |_, error|
24
+ messages << " #{error}:"
25
+ end
26
+ end
27
+ messages.join("\n")
28
+ end
29
+
30
+ failure_message_when_negated do |excel_path|
31
+ "expected excel:#{expected_excel_path} to not exactly match the content of #{excel_path} but it did."
32
+ end
33
+ end
34
+
35
+ # Specific Matcher class helper for comparing two excel documents.
36
+ class RSpec::Matchers::ExcelMatcher
37
+
38
+ def initialize
39
+ @errors = []
40
+ end
41
+
42
+ def match?
43
+ sheet_count_equal? && all_sheets_equal?
44
+ end
45
+
46
+ attr_accessor :errors, :expected_roo, :actual_roo
47
+
48
+ private
49
+
50
+ def check(sheet_name, check_result, fail_msg)
51
+ if check_result
52
+ true
53
+ else
54
+ errors << [sheet_name, fail_msg]
55
+ false
56
+ end
57
+ end
58
+
59
+ def sheet_count_equal?
60
+ check(:base, expected_roo.sheets.count == actual_roo.sheets.count, "Number of sheets do not match.")
61
+ end
62
+
63
+ def all_sheets_equal?
64
+ sheets(expected_roo).zip(sheets(actual_roo)).all? do |expected_sheet, actual_sheet|
65
+ sheets_equal?(expected_sheet, actual_sheet)
66
+ end
67
+ end
68
+
69
+ def sheets(roo)
70
+ (0 .. (roo.sheets.count - 1)).map do |i|
71
+ roo.sheet(i)
72
+ end
73
+ end
74
+
75
+ def sheets_equal?(expected_workbook, actual_workbook)
76
+ expected_workbook.sheets.all? do |sheet_name|
77
+ expected = expected_workbook.sheet(sheet_name)
78
+ actual = begin
79
+ actual_workbook.sheet(sheet_name)
80
+ rescue
81
+ nil
82
+ end
83
+
84
+ check(sheet_name, actual, "Sheet names do not match.") if actual != nil
85
+ # check(sheet_name, expected.first_row == actual.first_row, "Number of rows do not match.")
86
+ # check(sheet_name, expected.last_row == actual.last_row, "Number of rows do not match")
87
+ # check(sheet_name, expected.first_column == actual.first_column, "Number of columns do not match")
88
+ # check(sheet_name, expected.last_column == actual.last_column, "Number of columns do not match")
89
+ check_cells(sheet_name, expected, actual)
90
+
91
+ errors.empty?
92
+ end
93
+ end
94
+
95
+ def check_cells(sheet_name, sheet1, sheet2)
96
+ (sheet1.first_row .. sheet1.last_row).each do |row|
97
+ (sheet1.first_column .. sheet1.last_column).each do |col|
98
+ check(sheet_name, compare_value(col, row, sheet1, sheet2), discrepancy_message(col, row, sheet1, sheet2))
99
+ end
100
+ end
101
+ end
102
+
103
+ def compare_value(col, row, sheet1, sheet2)
104
+ value2 = sheet2.cell(row, col)
105
+ value1 = sheet1.cell(row, col)
106
+ if value2.class == Float
107
+ value2 == value1.to_f
108
+ elsif value1.class == Float
109
+ value1 == value2.to_f
110
+ else
111
+ value2 == value1
112
+ end
113
+ end
114
+
115
+ def discrepancy_message(col, row, sheet1, sheet2)
116
+ cell_id = "#{column_letter(col)}#{row}"
117
+ actual_value = sheet2.cell(row, col).inspect
118
+ expected_value = sheet1.cell(row, col).inspect
119
+ "Cell #{cell_id} actual:#{actual_value} expected:#{expected_value}"
120
+ end
121
+
122
+ def column_letter(col_number)
123
+ column_letters[col_number - 1]
124
+ end
125
+
126
+ def column_letters
127
+ @column_letters ||= ('A' .. 'ZZ').to_a
128
+ end
129
+ end
@@ -0,0 +1,4 @@
1
+ module ExcelTemplating
2
+ # excel_templating version
3
+ VERSION = "0.3.2"
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'excel_templating/version'
2
+ require 'excel_templating/renderer'
3
+ require 'excel_templating/document'
4
+ require 'excel_templating/render_helper'