iron-import 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/History.txt +12 -0
- data/LICENSE +20 -0
- data/README.rdoc +70 -0
- data/Version.txt +1 -0
- data/lib/iron/import/column.rb +177 -0
- data/lib/iron/import/csv_reader.rb +26 -0
- data/lib/iron/import/data_reader.rb +176 -0
- data/lib/iron/import/error.rb +66 -0
- data/lib/iron/import/importer.rb +188 -0
- data/lib/iron/import/row.rb +59 -0
- data/lib/iron/import/sheet.rb +186 -0
- data/lib/iron/import/xls_reader.rb +60 -0
- data/lib/iron/import/xlsx_reader.rb +60 -0
- data/lib/iron/import.rb +14 -0
- data/spec/importer/column_spec.rb +116 -0
- data/spec/importer/csv_reader_spec.rb +31 -0
- data/spec/importer/data_reader_spec.rb +93 -0
- data/spec/importer/importer_spec.rb +28 -0
- data/spec/importer/row_spec.rb +37 -0
- data/spec/importer/sheet_spec.rb +65 -0
- data/spec/importer/xlsx_reader_spec.rb +35 -0
- data/spec/samples/nanodrop.xlsx +0 -0
- data/spec/samples/simple.csv +4 -0
- data/spec/samples/test-products.xls +0 -0
- data/spec/spec_helper.rb +21 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 70c4748d780e9854cbd60622563b74d3b7ce2b5c
|
4
|
+
data.tar.gz: d6503f0f7a08b4c88da5813b3114446baf1fff1a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 488a0e4b2d8ed83914bb2a6c907358ee584c0849f26bf9e64d6cc4bd8c2296997e4bc580f59b3bff4db6fa699a6abf94f5a85cd31c1585f03f728523025529a3
|
7
|
+
data.tar.gz: 00c6e27cf433423c9c1cc14828c11cd895459b0c12e86aa57ec65b35b049b0b7939dda98edd3359aa4dab8af945b0a17b9ef5b7fc486300edeb8b987b21d65dd
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require <%= File.join(File.expand_path(File.dirname(__FILE__)), 'spec', 'spec_helper.rb') %>
|
data/History.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
== 0.5.0 / 2015-02-XX
|
2
|
+
|
3
|
+
* Initial revision
|
4
|
+
* Support for CSV, XLS and XLSX importing
|
5
|
+
* Multiple sheet support
|
6
|
+
* Automatic header and start-of-data detection
|
7
|
+
* Value coercion to :string, :integer, :float, :date, and :cents
|
8
|
+
* Custom parsing of raw cell values
|
9
|
+
* Custom validation of cell values
|
10
|
+
* Conditional row filtering
|
11
|
+
* Error and warning aggregation, by sheet/row as appropriate
|
12
|
+
* Automatic stream-to-file conversion where needed by underlying libs
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Irongaze Consulting LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
= GEM: iron-import
|
2
|
+
|
3
|
+
Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
Simple, reliable tabular data import.
|
8
|
+
|
9
|
+
This gem provides a set of classes to support automating import of tabular data from
|
10
|
+
CSV, XLS or XLSX files. Provides help in defining columns, auto-detecting column
|
11
|
+
order, pre-parsing data, and error/warning tracking.
|
12
|
+
|
13
|
+
The Roo/Spreadsheet gems do a great job of providing general purpose spreadsheet reading.
|
14
|
+
However, using them with unreliable user submitted data requires a lot of error checking,
|
15
|
+
monkeying with data coercion, etc. At Irongaze, we do a lot of work with growing
|
16
|
+
businesses, where Excel files are the lingua franca for all kinds of uses. This gem
|
17
|
+
attempts to extract years of experience building one-off importers into a simple library
|
18
|
+
for rapid import coding.
|
19
|
+
|
20
|
+
This is NOT a general-purpose tool for reading spreadsheets. If you want access to
|
21
|
+
cell styling, reading underlying formulas, etc., you will be better served building
|
22
|
+
a custom importer based on Roo. But if you're looking to take an uploaded CSV file,
|
23
|
+
validate and coerce values, then write each row to a database, all the while tracking
|
24
|
+
any warnings and errors encountered... well, this is the library for you!
|
25
|
+
|
26
|
+
IMPORTANT NOTE: this gem is in flux as we work to define the best possible abstraction
|
27
|
+
for the task. Breaking changes will be noted by increases in the second-level version,
|
28
|
+
ie 0.5.0 and 0.5.1 will be compatible, but 0.6.0 will not.
|
29
|
+
|
30
|
+
== SAMPLE USAGE
|
31
|
+
|
32
|
+
# Define our importer, with two columns. The importer will look for a row containing
|
33
|
+
# "name" and "description" (case insensitively) and automatically determine column
|
34
|
+
# order and starting row of the data.
|
35
|
+
importer = Importer.build do
|
36
|
+
column :name
|
37
|
+
column :description
|
38
|
+
end
|
39
|
+
|
40
|
+
# Import the provided file row-by-row if importing succeeds, automatically
|
41
|
+
# using the proper library to read CSV data. This same code would work
|
42
|
+
# with XLS or XLSX files with no changes to the code.
|
43
|
+
if importer.import('/tmp/source.csv')
|
44
|
+
importer.process do |row|
|
45
|
+
puts row[:name] + ' = ' + row[:description]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
== REQUIREMENTS
|
50
|
+
|
51
|
+
Depends on the iron-extensions and iron-dsl gems, and optionally requires the roo gem to support XLS and
|
52
|
+
XLSX file import and parsing. Without roo, all you get is CSV.
|
53
|
+
|
54
|
+
Requires RSpec and roo to build/test.
|
55
|
+
|
56
|
+
== INSTALLATION
|
57
|
+
|
58
|
+
To install, simply run:
|
59
|
+
|
60
|
+
sudo gem install iron-import
|
61
|
+
|
62
|
+
RVM users can skip the sudo:
|
63
|
+
|
64
|
+
gem install iron-import
|
65
|
+
|
66
|
+
Then use
|
67
|
+
|
68
|
+
require 'iron/import'
|
69
|
+
|
70
|
+
to require the library code.
|
data/Version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.0
|
@@ -0,0 +1,177 @@
|
|
1
|
+
class Importer
|
2
|
+
|
3
|
+
# Columns represent the settings for importing a given column within a Sheet. They do not
|
4
|
+
# hold data, rather they capture the settings needed for identifying the column in the header,
|
5
|
+
# how to parse and validate each of their cell's data, and so forth.
|
6
|
+
#
|
7
|
+
# Here's the complete list of column configuration options:
|
8
|
+
#
|
9
|
+
# Importer.build do
|
10
|
+
# column :key do
|
11
|
+
# # Set a fixed position - may be a column number or a letter-based
|
12
|
+
# # column description, ie 'A' == 1. In most cases, you can leave
|
13
|
+
# # this defaulted to nil, which will mean "look for the proper header"
|
14
|
+
# position 'C'
|
15
|
+
#
|
16
|
+
# # Specify a regex to locate the header for this column, defaults to
|
17
|
+
# # finding a string containing the key.
|
18
|
+
# header /(price|cost)/i
|
19
|
+
#
|
20
|
+
# # Tells the data parser what type of data this column contains, one
|
21
|
+
# # of :integer, :string, :date, :float, or :cents. Defaults to :string.
|
22
|
+
# type :cents
|
23
|
+
#
|
24
|
+
# # Instead of a type, you can set an explicit parse block. Be aware
|
25
|
+
# # that different source types may give you different raw values for what
|
26
|
+
# # seems like the "same" source value, for example an Excel source file
|
27
|
+
# # will give you a float value for all numeric types, even "integers"
|
28
|
+
# parse do |raw_value|
|
29
|
+
# raw_value.to_i + 1000
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # You can also add a custom validator to check the value and add
|
33
|
+
# # an error if it's not within a given range, or whatever:
|
34
|
+
# validate do |parsed_value|
|
35
|
+
# raise "Out of range" unless (parsed_value > 0 && parsed_value < 5000)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
class Column
|
41
|
+
|
42
|
+
# Holds load-time data
|
43
|
+
class Data
|
44
|
+
attr_accessor :index
|
45
|
+
|
46
|
+
def pos
|
47
|
+
@index ? Column::index_to_pos(@index) : 'Unknown'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Core info
|
52
|
+
attr_reader :key
|
53
|
+
attr_reader :data
|
54
|
+
|
55
|
+
# Configuration
|
56
|
+
dsl_flag :required
|
57
|
+
dsl_accessor :header, :position, :type
|
58
|
+
dsl_accessor :parse, :validate
|
59
|
+
|
60
|
+
def self.pos_to_index(pos)
|
61
|
+
raise 'Invalid column position: ' + pos.inspect unless pos.is_a?(String) && pos.match(/\A[a-z]{1,3}\z/i)
|
62
|
+
vals = pos.upcase.bytes.collect {|b| b - 64}
|
63
|
+
total = 0
|
64
|
+
multiplier = 1
|
65
|
+
vals.reverse.each do |val|
|
66
|
+
total += val * multiplier
|
67
|
+
multiplier *= 26
|
68
|
+
end
|
69
|
+
total - 1
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.index_to_pos(index)
|
73
|
+
val = index.to_i
|
74
|
+
raise 'Invalid column index: ' + index.inspect if (!index.is_a?(Fixnum) || index.to_i < 0)
|
75
|
+
|
76
|
+
chars = ('A'..'Z').to_a
|
77
|
+
str = ''
|
78
|
+
while index > 25
|
79
|
+
str = chars[index % 26] + str
|
80
|
+
index /= 26
|
81
|
+
index -= 1
|
82
|
+
end
|
83
|
+
str = chars[index] + str
|
84
|
+
str
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(sheet, key)
|
88
|
+
# Save off our info
|
89
|
+
@key = key
|
90
|
+
@sheet = sheet
|
91
|
+
@importer = @sheet.importer
|
92
|
+
|
93
|
+
# Return it as a string, by default
|
94
|
+
@type = :string
|
95
|
+
|
96
|
+
# By default, we allow empty values
|
97
|
+
@required = false
|
98
|
+
|
99
|
+
# Position can be explicitly set
|
100
|
+
@position = nil
|
101
|
+
|
102
|
+
# By default, don't parse incoming data, just pass it through
|
103
|
+
@parse = nil
|
104
|
+
|
105
|
+
# Default matcher, looks for the presence of the column key as text anywhere
|
106
|
+
# in the header string, ignoring case and using underscores as spaces, ie
|
107
|
+
# :order_id => /\A\s*order id\s*\z/i
|
108
|
+
@header = Regexp.new('\A\s*' + key.to_s.gsub('_', ' ') + '\s*\z', Regexp::IGNORECASE)
|
109
|
+
|
110
|
+
# Reset our state to pre-load status
|
111
|
+
reset
|
112
|
+
end
|
113
|
+
|
114
|
+
def build(&block)
|
115
|
+
DslProxy.exec(self, &block)
|
116
|
+
end
|
117
|
+
|
118
|
+
def reset
|
119
|
+
@data = Data.new
|
120
|
+
end
|
121
|
+
|
122
|
+
# When true, matches either the passed value or the index (if position has been explicitly set)
|
123
|
+
def match_header?(text, index)
|
124
|
+
res = index == self.fixed_index || (@header && !@header.match(text).nil?)
|
125
|
+
# puts "#{@header.inspect} ~ #{text.inspect} => #{res.inspect}"
|
126
|
+
res
|
127
|
+
end
|
128
|
+
|
129
|
+
# Use any custom parser defined to process the given value, capturing
|
130
|
+
# errors as needed
|
131
|
+
def parse_value(row, val)
|
132
|
+
return val if @parse.nil?
|
133
|
+
begin
|
134
|
+
@parse.call(val)
|
135
|
+
rescue Exception => e
|
136
|
+
@importer.add_error(row, "Error parsing #{self}: #{e}")
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate_value(row, val)
|
142
|
+
return unless @validate
|
143
|
+
begin
|
144
|
+
@validate.call(val)
|
145
|
+
true
|
146
|
+
rescue Exception => e
|
147
|
+
@importer.add_error(row, "Validation error in #{self}: #{e}")
|
148
|
+
false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def fixed_index
|
153
|
+
return nil unless @position
|
154
|
+
if @position.is_a?(Fixnum)
|
155
|
+
@position - 1
|
156
|
+
elsif @position.is_a?(String)
|
157
|
+
Column.pos_to_index(@position)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_s
|
162
|
+
'Column ' + @data.pos
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_a
|
166
|
+
@sheet.data.rows.collect {|r| r[@key] }
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_h
|
170
|
+
res = {}
|
171
|
+
@sheet.data.rows.collect {|r| res[r.num] = r[@key] }
|
172
|
+
res
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
class Importer
|
4
|
+
|
5
|
+
class CsvReader < DataReader
|
6
|
+
|
7
|
+
def initialize(importer)
|
8
|
+
super(importer, :csv)
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_stream(stream)
|
12
|
+
text = stream.read
|
13
|
+
encoding = @importer.encoding || 'UTF-8'
|
14
|
+
raw_rows = CSV.parse(text, :encoding => "#{encoding}:UTF-8")
|
15
|
+
@importer.default_sheet.parse_raw_data(raw_rows)
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_file(path)
|
19
|
+
encoding = @importer.encoding || 'UTF-8'
|
20
|
+
raw_rows = CSV.read(path, :encoding => "#{encoding}:UTF-8")
|
21
|
+
@importer.default_sheet.parse_raw_data(raw_rows)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
class Importer
|
2
|
+
|
3
|
+
# Base class for our input reading - dealing with the raw file/stream,
|
4
|
+
# and extracting raw values. In addition, we provide the base
|
5
|
+
# data coercion/parsing for our derived classes.
|
6
|
+
class DataReader
|
7
|
+
|
8
|
+
# Attributes
|
9
|
+
attr_reader :format
|
10
|
+
|
11
|
+
def self.verify_roo!
|
12
|
+
if Gem::Specification.find_all_by_name('roo', '~> 1.13.0').empty?
|
13
|
+
raise "You are attempting to use the iron-import gem to import an Excel file. Doing so requires installing the roo gem, version 1.13.0 or later."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.for_format(importer, format)
|
18
|
+
case format
|
19
|
+
when :csv
|
20
|
+
CsvReader.new(importer)
|
21
|
+
when :xls
|
22
|
+
verify_roo!
|
23
|
+
XlsReader.new(importer)
|
24
|
+
when :xlsx
|
25
|
+
verify_roo!
|
26
|
+
XlsxReader.new(importer)
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.for_path(importer, path)
|
33
|
+
format = path.to_s.extract(/\.(csv|xlsx?)\z/i)
|
34
|
+
if format
|
35
|
+
format = format.downcase.to_sym
|
36
|
+
for_format(importer, format)
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.for_stream(importer, stream)
|
43
|
+
path = path_from_stream(stream)
|
44
|
+
for_path(importer, path)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Try to find the original file name for the given stream,
|
48
|
+
# as in the case where a file is uploaded to Rails and we're dealing with an
|
49
|
+
# ActionDispatch::Http::UploadedFile.
|
50
|
+
def self.path_from_stream(stream)
|
51
|
+
if stream.respond_to?(:original_filename)
|
52
|
+
stream.original_filename
|
53
|
+
elsif stream.respond_to?(:path)
|
54
|
+
stream.path
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(importer, format)
|
61
|
+
@importer = importer
|
62
|
+
@format = format
|
63
|
+
@multisheet = true
|
64
|
+
end
|
65
|
+
|
66
|
+
def load(path_or_stream)
|
67
|
+
# Figure out what we've been passed, and handle it
|
68
|
+
if path_or_stream.respond_to?(:read)
|
69
|
+
# We have a stream (open file, upload, whatever)
|
70
|
+
if respond_to?(:load_stream)
|
71
|
+
# Stream loader defined, run it
|
72
|
+
load_stream(path_or_stream)
|
73
|
+
else
|
74
|
+
# Write to temp file, as some of our readers only read physical files, annoyingly
|
75
|
+
file = Tempfile.new(['importer', ".#{format}"])
|
76
|
+
file.binmode
|
77
|
+
begin
|
78
|
+
file.write path_or_stream.read
|
79
|
+
file.close
|
80
|
+
load_file(file.path)
|
81
|
+
ensure
|
82
|
+
file.close
|
83
|
+
file.unlink
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
elsif path_or_stream.is_a?(String)
|
88
|
+
# Assume it's a path
|
89
|
+
if respond_to?(:load_file)
|
90
|
+
# We're all set, load up the given path
|
91
|
+
load_file(path_or_stream)
|
92
|
+
else
|
93
|
+
# No file handler, so open the file and run the stream processor
|
94
|
+
file = File.open(path_or_stream, 'rb')
|
95
|
+
load_stream(file)
|
96
|
+
end
|
97
|
+
|
98
|
+
else
|
99
|
+
raise "Unable to load data: #{path_or_stream.inspect}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return our status
|
103
|
+
!@importer.has_errors?
|
104
|
+
end
|
105
|
+
|
106
|
+
# Provides default value parsing/coersion for all derived data readers. Attempts to be clever and
|
107
|
+
# handle edge cases like converting '5.00' to 5 when in integer mode, etc. If you find your inputs aren't
|
108
|
+
# being parsed correctly, add a custom #parse block on your Column definition.
|
109
|
+
def parse_value(val, type)
|
110
|
+
return nil if val.nil? || val.to_s == ''
|
111
|
+
|
112
|
+
case type
|
113
|
+
when :string then
|
114
|
+
val = val.to_s.strip
|
115
|
+
val.blank? ? nil : val
|
116
|
+
|
117
|
+
when :integer, :int then
|
118
|
+
if val.class < Numeric
|
119
|
+
# If numeric, verify that there's no decimal places to worry about
|
120
|
+
if (val.to_f % 1.0 == 0.0)
|
121
|
+
val.to_i
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
else
|
126
|
+
# Convert to string, strip off trailing decimal zeros
|
127
|
+
val = val.to_s.strip.gsub(/\.0*$/, '')
|
128
|
+
if val.integer?
|
129
|
+
val.to_i
|
130
|
+
else
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
when :float then
|
136
|
+
if val.class < Numeric
|
137
|
+
val.to_f
|
138
|
+
else
|
139
|
+
# Convert to string, strip off trailing decimal zeros
|
140
|
+
val = val.to_s.strip
|
141
|
+
if val.match(/\A-?[0-9]+(?:\.[0-9]+)?\z/)
|
142
|
+
val.to_f
|
143
|
+
else
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
when :cents then
|
149
|
+
if val.is_a?(String)
|
150
|
+
val = val.gsub(/\s*\$\s*/, '')
|
151
|
+
end
|
152
|
+
intval = parse_value(val, :integer)
|
153
|
+
if !val.is_a?(Float) && intval
|
154
|
+
intval * 100
|
155
|
+
else
|
156
|
+
floatval = parse_value(val, :float)
|
157
|
+
if floatval
|
158
|
+
(floatval * 100).to_i
|
159
|
+
else
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
when :date then
|
165
|
+
# Pull out the date part of the string and convert
|
166
|
+
date_str = val.to_s.extract(/[0-9]+[\-\/][0-9]+[\-\/][0-9]+/)
|
167
|
+
date_str.to_date rescue nil
|
168
|
+
|
169
|
+
else
|
170
|
+
raise "Unknown column type #{type.inspect} - unimplemented?"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Importer
|
2
|
+
|
3
|
+
class Error
|
4
|
+
|
5
|
+
attr_reader :sheet, :row, :text
|
6
|
+
|
7
|
+
def initialize(context, text)
|
8
|
+
if context.is_a?(Importer::Sheet)
|
9
|
+
@sheet = context
|
10
|
+
elsif context.is_a?(Importer::Row)
|
11
|
+
@row = context
|
12
|
+
@sheet = context.sheet
|
13
|
+
end
|
14
|
+
@text = text.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def summary
|
18
|
+
summary = ''
|
19
|
+
if @row
|
20
|
+
summary += "#{@sheet} #{@row}: "
|
21
|
+
elsif @sheet
|
22
|
+
summary += "#{@sheet}: "
|
23
|
+
end
|
24
|
+
summary + @text
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
summary
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the level at which this error occurred, one of
|
32
|
+
# :row, :sheet, :importer
|
33
|
+
def level
|
34
|
+
return :row if @row
|
35
|
+
return :sheet if @sheet
|
36
|
+
return :importer
|
37
|
+
end
|
38
|
+
|
39
|
+
def row_level?
|
40
|
+
level == :row
|
41
|
+
end
|
42
|
+
|
43
|
+
def sheet_level?
|
44
|
+
level == :sheet
|
45
|
+
end
|
46
|
+
|
47
|
+
def importer_level?
|
48
|
+
level == :importer
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true if this error is for the given context, where
|
52
|
+
# context can be a Row, Sheet or Importer instance.
|
53
|
+
def for_context?(context)
|
54
|
+
case context
|
55
|
+
when Row
|
56
|
+
return @row == context
|
57
|
+
when Sheet
|
58
|
+
return @sheet == context
|
59
|
+
else
|
60
|
+
return true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|