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.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.hound.yml +261 -0
- data/.rubocop.ph.yml +44 -0
- data/.rubocop.yml +3 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +8 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +3 -0
- data/README.md +133 -0
- data/Rakefile +43 -0
- data/excel_templating.gemspec +32 -0
- data/lib/excel_templating/document/data_source_registry/registry_list.rb +48 -0
- data/lib/excel_templating/document/data_source_registry/registry_renderer.rb +74 -0
- data/lib/excel_templating/document/data_source_registry.rb +64 -0
- data/lib/excel_templating/document/sheet/repeated_row.rb +39 -0
- data/lib/excel_templating/document/sheet.rb +133 -0
- data/lib/excel_templating/document.rb +71 -0
- data/lib/excel_templating/document_dsl.rb +85 -0
- data/lib/excel_templating/excel_abstraction/active_cell_reference.rb +59 -0
- data/lib/excel_templating/excel_abstraction/cell.rb +23 -0
- data/lib/excel_templating/excel_abstraction/cell_range.rb +26 -0
- data/lib/excel_templating/excel_abstraction/cell_reference.rb +39 -0
- data/lib/excel_templating/excel_abstraction/date.rb +36 -0
- data/lib/excel_templating/excel_abstraction/row.rb +29 -0
- data/lib/excel_templating/excel_abstraction/sheet.rb +102 -0
- data/lib/excel_templating/excel_abstraction/spread_sheet.rb +28 -0
- data/lib/excel_templating/excel_abstraction/time.rb +42 -0
- data/lib/excel_templating/excel_abstraction/work_book.rb +47 -0
- data/lib/excel_templating/excel_abstraction.rb +16 -0
- data/lib/excel_templating/render_helper.rb +14 -0
- data/lib/excel_templating/renderer.rb +251 -0
- data/lib/excel_templating/rspec_excel_matcher.rb +129 -0
- data/lib/excel_templating/version.rb +4 -0
- data/lib/excel_templating.rb +4 -0
- data/spec/assets/alphalist_7_4.mustache.xlsx +0 -0
- data/spec/assets/alphalist_seven_four_expected.xlsx +0 -0
- data/spec/assets/valid_cell.mustache.xlsx +0 -0
- data/spec/assets/valid_cell_expected.xlsx +0 -0
- data/spec/assets/valid_cell_expected_inline.xlsx +0 -0
- data/spec/assets/valid_column_expected.xlsx +0 -0
- data/spec/cell_validation_spec.rb +114 -0
- data/spec/column_validation_spec.rb +47 -0
- data/spec/excel_abstraction/active_cell_reference_spec.rb +73 -0
- data/spec/excel_abstraction/cell_range_spec.rb +36 -0
- data/spec/excel_abstraction/cell_reference_spec.rb +69 -0
- data/spec/excel_abstraction/cell_spec.rb +54 -0
- data/spec/excel_abstraction/date_spec.rb +27 -0
- data/spec/excel_abstraction/row_spec.rb +42 -0
- data/spec/excel_abstraction/sheet_spec.rb +83 -0
- data/spec/excel_abstraction/spread_sheet_spec.rb +35 -0
- data/spec/excel_abstraction/time_spec.rb +27 -0
- data/spec/excel_abstraction/work_book_spec.rb +22 -0
- data/spec/excel_helper.rb +16 -0
- data/spec/excel_templating_spec.rb +141 -0
- data/spec/spec_helper.rb +13 -0
- metadata +281 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# A registry for validation data sources within the excel spreadsheet DSL
|
3
|
+
# Supports Enumerable#each for iterating the registry entries.
|
4
|
+
class Document::DataSourceRegistry
|
5
|
+
include Enumerable
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# Create an empty DataSourceRegistry
|
9
|
+
def initialize
|
10
|
+
@source_symbols = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Hash] data
|
14
|
+
# @return [RegistryRenderer]
|
15
|
+
def renderer(data:)
|
16
|
+
RegistryRenderer.new(self, data)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Symbol] source_symbol
|
20
|
+
# @param [String] title
|
21
|
+
# @param [Array<String>|Symbol] list
|
22
|
+
# @param [TrueClass|FalseClass] inline
|
23
|
+
def add_list(source_symbol, title, list, inline)
|
24
|
+
source_symbols[source_symbol] = RegistryList.new(source_symbols.size + 1, source_symbol, title, list, inline)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Symbol] source_symbol
|
28
|
+
# @return [RegistryList]
|
29
|
+
def [](source_symbol)
|
30
|
+
source_symbols[source_symbol]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Symbol] source_symbol
|
34
|
+
def has_registry?(source_symbol)
|
35
|
+
source_symbols.has_key?(source_symbol)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [TrueClass|FalseClass]
|
39
|
+
def any_data_sheet_symbols?
|
40
|
+
select {|info|
|
41
|
+
info.data_sheet?
|
42
|
+
}.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array<Symbol>]
|
46
|
+
def supported_registries
|
47
|
+
source_symbols.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
delegate [:each] => :ordered_registries
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :source_symbols
|
55
|
+
|
56
|
+
def ordered_registries
|
57
|
+
source_symbols.values.sort_by(&:order)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require_relative 'data_source_registry/registry_renderer'
|
64
|
+
require_relative 'data_source_registry/registry_list'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# Simple class for representing a repeated row on a sheet.
|
3
|
+
class Document::Sheet::RepeatedRow
|
4
|
+
# @param [Integer] row_number
|
5
|
+
# @param [Symol] data_attribute
|
6
|
+
def initialize(row_number, data_attribute)
|
7
|
+
@row_number = row_number
|
8
|
+
@data_attribute = data_attribute
|
9
|
+
@column_validations = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
### Dsl Methods ###
|
13
|
+
|
14
|
+
# Validate a particular row in a repeated set as being part of a declared data source
|
15
|
+
# @example
|
16
|
+
# validate_column 5, with: :valid_foos
|
17
|
+
# @param [Integer] column_number
|
18
|
+
# @param [Symbol] with
|
19
|
+
def validate_column(column_number, with:)
|
20
|
+
@column_validations[column_number] = with
|
21
|
+
end
|
22
|
+
|
23
|
+
### Non Dsl Methods ###
|
24
|
+
|
25
|
+
attr_reader :row_number, :data_attribute
|
26
|
+
|
27
|
+
# @param [Integer] column_number
|
28
|
+
# @return [Symbol] Registered source at that column
|
29
|
+
def validated_column_source(column_number)
|
30
|
+
@column_validations[column_number]
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Integer] column_number
|
34
|
+
# @return [TrueClass|FalseClass]
|
35
|
+
def validated_column?(column_number)
|
36
|
+
@column_validations.has_key?(column_number)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# Define a sheet on a document
|
3
|
+
# @example
|
4
|
+
# sheet 1 do
|
5
|
+
# repeat_row 17, with: :people
|
6
|
+
# end
|
7
|
+
class Document::Sheet
|
8
|
+
# @param [Integer] sheet_number
|
9
|
+
def initialize(sheet_number)
|
10
|
+
@sheet_number = sheet_number
|
11
|
+
@repeated_rows = {}
|
12
|
+
@validated_cells = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
### Sheet Dsl Methods ####
|
16
|
+
|
17
|
+
# @param [Float] decimal_inches
|
18
|
+
# @return [Float] inches converted to excel integer size.
|
19
|
+
def inches(decimal_inches)
|
20
|
+
# empirically determined number. 30.0 seems to be the measurement for 2.6 inches
|
21
|
+
# in open office.
|
22
|
+
(30.0 / 2.6) * decimal_inches
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [Hash] default default styling for all columns.
|
26
|
+
# @param [Hash] columns specific styling for numbered columns.
|
27
|
+
def style_columns(default:, columns: {})
|
28
|
+
@default_column_style = default
|
29
|
+
@column_styles = columns
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [Hash] default default styling for all rows.
|
33
|
+
# @param [Hash] rows specific styling for numbered rows.
|
34
|
+
def style_rows(default:, rows: {})
|
35
|
+
@default_row_style = default
|
36
|
+
@row_styles = rows
|
37
|
+
end
|
38
|
+
|
39
|
+
# Repeat a numbered row in the template using an array from the data
|
40
|
+
# will result in expanding the produced excel document by a number of rows.
|
41
|
+
# it is expected that the sheet specific data will contain :with as an Array.
|
42
|
+
# @example
|
43
|
+
# repeat_row 17, with: :employee_data
|
44
|
+
# @param [Integer] row_number
|
45
|
+
# @param [Symbol] with
|
46
|
+
def repeat_row(row_number, with:, &block)
|
47
|
+
repeated_rows[row_number] = RepeatedRow.new(row_number, with)
|
48
|
+
repeated_rows[row_number].instance_eval(&block) if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Validate a particular cell using a declared data source
|
52
|
+
# @example
|
53
|
+
# validate_cell row: 1, column :5, with: :valid_foos
|
54
|
+
# @param [Integer] row
|
55
|
+
# @param [Integer] column
|
56
|
+
# @param [Symbol] with
|
57
|
+
def validate_cell(row:, column:, with:)
|
58
|
+
validated_cells["#{row}:#{column}"] = with
|
59
|
+
end
|
60
|
+
|
61
|
+
#### Non DSL Methods ###
|
62
|
+
|
63
|
+
def default_column_style
|
64
|
+
@default_column_style || {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def column_styles
|
68
|
+
@column_styles || {}
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_row_style
|
72
|
+
@default_row_style || {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def row_styles
|
76
|
+
@row_styles || {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def sheet_data(data)
|
80
|
+
data[sheet_number] || {}
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param [Integer] row_number
|
84
|
+
def repeated_row?(row_number)
|
85
|
+
repeated_rows.has_key?(row_number)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [Integer] row_number
|
89
|
+
# @param [Integer] column_number
|
90
|
+
def validated_cell?(row_number, column_number)
|
91
|
+
(repeated_row?(row_number) && repeated_rows[row_number].validated_column?(column_number)) ||
|
92
|
+
validated_cells.has_key?("#{row_number}:#{column_number}")
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [Integer] row_number
|
96
|
+
# @param [Integer] column_number
|
97
|
+
# @return [Symbol] The registered symbol for that row & column or Nil
|
98
|
+
def validation_source_name(row_number, column_number)
|
99
|
+
if repeated_row?(row_number)
|
100
|
+
repeated_rows[row_number].validated_column_source(column_number)
|
101
|
+
else
|
102
|
+
validated_cells["#{row_number}:#{column_number}"]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Repeat each row of the data if it is repeated, yielding each item in succession.
|
107
|
+
# @param [Integer] row_number
|
108
|
+
# @param [Hash] sheet_data Data for this sheet
|
109
|
+
def each_row_at(row_number, sheet_data)
|
110
|
+
if repeated_row?(row_number)
|
111
|
+
repeater = repeated_rows[row_number]
|
112
|
+
verify_array!(sheet_data, repeater.data_attribute)
|
113
|
+
sheet_data[repeater.data_attribute].each_with_index do |row_data, index|
|
114
|
+
yield({ index: index }.merge(row_data).merge(sheet_data))
|
115
|
+
end
|
116
|
+
else
|
117
|
+
yield sheet_data
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :sheet_number, :repeated_rows, :validated_cells
|
124
|
+
|
125
|
+
def verify_array!(sheet_data, attribute)
|
126
|
+
unless sheet_data[attribute].is_a?(Array)
|
127
|
+
raise ArgumentError, "Data for sheet #{sheet_number} did not contain #{attribute} array as expected!"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
require_relative 'sheet/repeated_row'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'document_dsl'
|
2
|
+
|
3
|
+
module ExcelTemplating
|
4
|
+
# The base document class for an ExcelTemplating.
|
5
|
+
# Inherit from document to create your own ExcelTemplating that you may then use to generate
|
6
|
+
# Excel Spreadsheets from the template you supply.
|
7
|
+
# @example
|
8
|
+
# class MyTemplate < ExcelTemplating::Document
|
9
|
+
# template "my_template.mustache.xlsx"
|
10
|
+
# title "My title: {{my_company}}"
|
11
|
+
# organization "{{organization_name}}"
|
12
|
+
# default_styling(
|
13
|
+
# text_wrap: 0,
|
14
|
+
# font: "Calibri",
|
15
|
+
# size: 10,
|
16
|
+
# align: :left,
|
17
|
+
# )
|
18
|
+
# sheet 1 do
|
19
|
+
# repeat_row 17, with: :people
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
class Document
|
23
|
+
extend DocumentDsl
|
24
|
+
class << self
|
25
|
+
## The non Dsl Methods, not expected to be used as part of the document description
|
26
|
+
# @return [String] The document title
|
27
|
+
def document_title
|
28
|
+
@document_title
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] The document organization
|
32
|
+
def document_organization
|
33
|
+
@document_organization
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Hash] The default styling for the document
|
37
|
+
def document_default_styling
|
38
|
+
@default_styling || default_styling
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Create a new document with given data. 'all_sheets' is available to the template on each sheet.
|
42
|
+
# otherwise each numeric key in 'sheet_data' provides the data for that specific sheet.
|
43
|
+
# For example {all_sheets: {foo: 'bar'}, 1 => {var1: "foo"}}
|
44
|
+
# @param [Hash] data Hash with variables for rendering.
|
45
|
+
def initialize(data)
|
46
|
+
@data = data
|
47
|
+
end
|
48
|
+
|
49
|
+
# Render this template.
|
50
|
+
# @example
|
51
|
+
# instance = MyTemplate.new(all_sheets: {foo: 1},1 => {bar: "foo"})
|
52
|
+
# instance.render do |file_path|
|
53
|
+
# FileUtils.cp(file_path, somewhere_else)
|
54
|
+
# end
|
55
|
+
def render(&block)
|
56
|
+
new_renderer.render(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :data
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def new_renderer
|
64
|
+
ExcelTemplating::Renderer.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
require_relative 'document/sheet'
|
71
|
+
require_relative 'document/data_source_registry'
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ExcelTemplating
|
2
|
+
# The descriptor module for how to define your template class
|
3
|
+
module DocumentDsl
|
4
|
+
# @return [String] The template path.
|
5
|
+
def template_path
|
6
|
+
@template_path
|
7
|
+
end
|
8
|
+
|
9
|
+
# @param [String] path Set the path to the template for this document class.
|
10
|
+
def template(path)
|
11
|
+
raise ArgumentError, "Template path must be a string." unless path.is_a?(String)
|
12
|
+
@template_path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<ExcelTemplating::Document::Sheet>] The sheets defined for this document class.
|
16
|
+
def sheets
|
17
|
+
@sheets ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Define a sheet on this document
|
21
|
+
# @example
|
22
|
+
# sheet 1 do
|
23
|
+
# repeat_row 17, with: :people
|
24
|
+
# end
|
25
|
+
# @param [Integer] sheet_number
|
26
|
+
# @param [Proc] block
|
27
|
+
def sheet(sheet_number, &block)
|
28
|
+
sheet = ExcelTemplating::Document::Sheet.new(sheet_number)
|
29
|
+
sheets << sheet
|
30
|
+
sheet.instance_eval(&block)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def protect_document(protect=true)
|
35
|
+
@protected = protect
|
36
|
+
end
|
37
|
+
|
38
|
+
def protected?
|
39
|
+
!!@protected
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add a list validator to the excel document
|
43
|
+
# @example
|
44
|
+
# list_source :valid_foos, title: "Valid Foos", list: ['foo','bar'], inline: false
|
45
|
+
# @param [Symbol] source_symbol symbol to registry for the validator
|
46
|
+
# @param [String] title Title to show when displaying this validator
|
47
|
+
# @param [Array<String>|Symbol] list items to use for validation, you may also use :from_data and at render time
|
48
|
+
# the validation items will be fetched from key 'source_symbol'
|
49
|
+
# @param [TrueClass|FalseClass] inline If true then the validator will be written to the document inline.
|
50
|
+
# Otherwise it will be written to a 'DataSheet'
|
51
|
+
def list_source(source_symbol, title:, list: :from_data, inline: false)
|
52
|
+
data_source_registry.add_list(source_symbol, title, list, inline)
|
53
|
+
end
|
54
|
+
|
55
|
+
def data_source_registry
|
56
|
+
@data_source_registry ||= ExcelTemplating::Document::DataSourceRegistry.new
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define a title for this workbook. You may use mustaching here.
|
60
|
+
# @param [String] string
|
61
|
+
def title(string)
|
62
|
+
@document_title = string
|
63
|
+
end
|
64
|
+
|
65
|
+
# Define the default styling to use when writing
|
66
|
+
# a column to the worksheet. See Writeexcel::Format
|
67
|
+
# @param [String] font Set the font name
|
68
|
+
# @param [Integer] size font size
|
69
|
+
# @param [Symbol] align :left, :right, or :center
|
70
|
+
# @param [Hash] options Additional options to pass to Format
|
71
|
+
def default_styling(font: "Calibri", size: 10, align: :left, ** options)
|
72
|
+
@default_styling = {
|
73
|
+
name: font,
|
74
|
+
size: size,
|
75
|
+
align: align
|
76
|
+
}.merge(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Define an organization for the workbook. May use mustaching.
|
80
|
+
# @param [String] string
|
81
|
+
def organization(string)
|
82
|
+
@document_organization = string
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class ActiveCellReference
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :position, :row, :col
|
6
|
+
|
7
|
+
def initialize(attrs = {})
|
8
|
+
@position = ExcelAbstraction::CellReference.new(attrs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def move(directions = {})
|
12
|
+
directions.each do |type, times|
|
13
|
+
self.respond_to?(type) ? self.__send__(type, times) : raise(ArgumentError.new("Movement direction is not valid."))
|
14
|
+
end
|
15
|
+
position
|
16
|
+
end
|
17
|
+
|
18
|
+
def up(times = 1)
|
19
|
+
goto(row - times, col)
|
20
|
+
end
|
21
|
+
|
22
|
+
def down(times = 1)
|
23
|
+
goto(row + times, col)
|
24
|
+
end
|
25
|
+
|
26
|
+
def left(times = 1)
|
27
|
+
goto(row, col - times)
|
28
|
+
end
|
29
|
+
|
30
|
+
def right(times = 1)
|
31
|
+
goto(row, col + times)
|
32
|
+
end
|
33
|
+
|
34
|
+
def carriage_return
|
35
|
+
goto(row, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def linefeed
|
39
|
+
down
|
40
|
+
end
|
41
|
+
|
42
|
+
def newline
|
43
|
+
carriage_return
|
44
|
+
linefeed
|
45
|
+
end
|
46
|
+
|
47
|
+
def goto(row, col)
|
48
|
+
self.position = ExcelAbstraction::CellReference.new(row: row, col: col)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset
|
52
|
+
self.position = ExcelAbstraction::CellReference.new(row: 0, col: 0)
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
attr_accessor :position
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class Cell
|
3
|
+
attr_reader :position, :val, :styles
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@position = Integer(attrs.fetch(:position) { raise ArgumentError.new("Position absent for ExcelAbstraction cell") })
|
7
|
+
@val = attrs.fetch(:val) { raise ArgumentError.new("Value absent for ExcelAbstraction cell") }
|
8
|
+
@styles = attrs.fetch(:styles) { {} }
|
9
|
+
end
|
10
|
+
|
11
|
+
def <=>(other)
|
12
|
+
position <=> other.position
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
position == other.position && val == other.val && styles == other.styles
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_cell
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class CellRange
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
alias_method :first, :min
|
6
|
+
alias_method :last, :max
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@cell_references = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
cell_references.each { |cell_reference| yield(cell_reference) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(attrs)
|
17
|
+
cell_reference = ExcelAbstraction::CellReference.new(attrs)
|
18
|
+
raise(ArgumentError, "Must be a CellReference belonging to the same row") if last && last.row != cell_reference.row
|
19
|
+
self.cell_references << cell_reference
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
attr_reader :cell_references
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class CellReference
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
COLS = ('A'..'ZZ').to_a
|
6
|
+
|
7
|
+
attr_accessor :row, :col
|
8
|
+
|
9
|
+
def initialize(attrs = {})
|
10
|
+
@row = attrs.fetch(:row) { 0 }
|
11
|
+
@col = attrs.fetch(:col) { 0 }
|
12
|
+
end
|
13
|
+
|
14
|
+
def <=>(other)
|
15
|
+
other = other.to_cell_reference
|
16
|
+
(self.row == other.row) ? (self.col <=> other.col) : (self.row <=> other.row)
|
17
|
+
end
|
18
|
+
|
19
|
+
def succ
|
20
|
+
self.class.new(row: self.row, col: self.col + 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
COLS[col] + (row + 1).to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_cell_reference
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_ary
|
32
|
+
[row, col]
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_a
|
36
|
+
to_ary
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module ExcelAbstraction
|
4
|
+
class Date < DelegateClass(Float)
|
5
|
+
ADJUSTMENT = ::Date.parse("1900-03-01")
|
6
|
+
REFERENCE = ::Date.parse("1900-01-01")
|
7
|
+
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
def initialize(raw_value)
|
11
|
+
super(convert(raw_value))
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_excel_date
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def reference
|
21
|
+
REFERENCE
|
22
|
+
end
|
23
|
+
|
24
|
+
def adjustment
|
25
|
+
ADJUSTMENT
|
26
|
+
end
|
27
|
+
|
28
|
+
def adjust(raw_value)
|
29
|
+
adjustment < raw_value ? 2 : 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def convert(raw_value)
|
33
|
+
(raw_value - reference + adjust(raw_value)).to_f
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ExcelAbstraction
|
2
|
+
class Row
|
3
|
+
include Enumerable
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_accessor :styles
|
7
|
+
delegate [:each] => :cells
|
8
|
+
|
9
|
+
alias :first :min
|
10
|
+
alias :last :max
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@cells = []
|
14
|
+
@styles = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](index)
|
18
|
+
find { |cell| cell.position == index }
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(attrs)
|
22
|
+
@cells << ExcelAbstraction::Cell.new(attrs)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
attr_accessor :cells
|
28
|
+
end
|
29
|
+
end
|