gs1 0.1.1 → 0.1.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 +4 -4
- data/lib/gs1/barcode/base.rb +11 -55
- data/lib/gs1/barcode/healthcare.rb +1 -1
- data/lib/gs1/barcode/segment.rb +126 -0
- data/lib/gs1/barcode/tokenizer.rb +46 -0
- data/lib/gs1/barcode.rb +10 -0
- data/lib/gs1/definitions.rb +8 -6
- data/lib/gs1/extensions/gtin.rb +2 -0
- data/lib/gs1/record.rb +1 -1
- data/lib/gs1/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 524381a860829321e654f5751256113a19ba135a
|
4
|
+
data.tar.gz: e134536208ea7e10bce1e53ead1d2e57ea31c4a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdc1e69f3e91208c624e14bb1c7300f0e81b6c1a371aa909afe44bb644b1ecef54aa8dd62fad7274fb9dc111f0a33917a85eeffcb05ce241cc52e7447148f037
|
7
|
+
data.tar.gz: 1b1ca6dd02ac6220a2fef92884c22f8a900030c285df815da1bc11b8890f8d347e533d2de2b11cf6e179ccfdb630f2c59839d44163ad699bb207083f3fb2d091
|
data/lib/gs1/barcode/base.rb
CHANGED
@@ -5,77 +5,33 @@ module GS1
|
|
5
5
|
class Base
|
6
6
|
include Definitions
|
7
7
|
|
8
|
-
class UnknownRecordError < StandardError; end
|
9
|
-
|
10
|
-
attr_reader :errors
|
11
|
-
|
12
8
|
def initialize(options = {})
|
13
9
|
self.class.records.each do |record|
|
14
10
|
data = options.fetch(record.underscore_name, nil)
|
15
11
|
|
16
12
|
instance_variable_set("@#{record.underscore_name}", record.new(data))
|
17
13
|
end
|
14
|
+
end
|
18
15
|
|
19
|
-
|
16
|
+
def errors
|
17
|
+
@errors ||= []
|
20
18
|
end
|
21
19
|
|
22
20
|
class << self
|
23
|
-
def from_scan(barcode, separator:
|
24
|
-
new(scan_to_params(barcode, separator: separator))
|
25
|
-
end
|
26
|
-
|
27
|
-
def scan_to_params(barcode, separator: "\u001E")
|
28
|
-
data = barcode.chars
|
29
|
-
|
30
|
-
{}.tap do |params|
|
31
|
-
params.merge!(scan_to_param(data, separator)) until data.empty?
|
32
|
-
end
|
21
|
+
def from_scan!(barcode, separator: DEFAULT_SEPARATOR)
|
22
|
+
new(scan_to_params!(barcode, separator: separator))
|
33
23
|
end
|
34
24
|
|
35
|
-
|
36
|
-
|
37
|
-
def scan_to_param(data, separator)
|
38
|
-
record = record_from_data(data)
|
39
|
-
|
40
|
-
{ record.underscore_name => shift_length(data, record, separator) }
|
41
|
-
end
|
42
|
-
|
43
|
-
# Fetch the two first characters (interpreted as AI) from the remaining
|
44
|
-
# data and try to find record class. If no record class was found, fetch
|
45
|
-
# a third character and try again, and then finally a forth, as no AI
|
46
|
-
# currently have more then 4 characters.
|
47
|
-
def record_from_data(data)
|
48
|
-
ai_variants = []
|
49
|
-
|
50
|
-
record = process_ai_variants(ai_variants, data, 2) ||
|
51
|
-
process_ai_variants(ai_variants, data, 1) ||
|
52
|
-
process_ai_variants(ai_variants, data, 1)
|
53
|
-
|
54
|
-
return record if record
|
55
|
-
|
56
|
-
raise UnknownRecordError, "Unable to retrieve record from application identifier(s) #{ai_variants.join(', ')}"
|
57
|
-
end
|
58
|
-
|
59
|
-
def process_ai_variants(ai_variants, data, shifts)
|
60
|
-
ai_variants << ai_variants.last.to_s + data.shift(shifts).join
|
61
|
-
|
62
|
-
AI_CLASSES[ai_variants.last]
|
25
|
+
def from_scan(barcode, separator: DEFAULT_SEPARATOR)
|
26
|
+
new(scan_to_params(barcode, separator: separator))
|
63
27
|
end
|
64
28
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
if data.find_index(separator) && data.find_index(separator) < record.barcode_max_length
|
69
|
-
shift_variable_length(data, separator)
|
70
|
-
else
|
71
|
-
data.shift(record.barcode_max_length).join
|
72
|
-
end
|
29
|
+
def scan_to_params!(barcode, separator: DEFAULT_SEPARATOR)
|
30
|
+
Tokenizer.new(barcode, separator: separator).to_params!
|
73
31
|
end
|
74
32
|
|
75
|
-
def
|
76
|
-
|
77
|
-
data.shift unless data.empty? # Shift separator character
|
78
|
-
end
|
33
|
+
def scan_to_params(barcode, separator: DEFAULT_SEPARATOR)
|
34
|
+
Tokenizer.new(barcode, separator: separator).to_params
|
79
35
|
end
|
80
36
|
end
|
81
37
|
end
|
@@ -5,7 +5,7 @@ module GS1
|
|
5
5
|
class Healthcare < Base
|
6
6
|
define_records GTIN, ExpirationDate, Batch, SerialNumber
|
7
7
|
|
8
|
-
def to_s(level: AIDCMarketingLevels::ENHANCED, separator:
|
8
|
+
def to_s(level: AIDCMarketingLevels::ENHANCED, separator: DEFAULT_SEPARATOR)
|
9
9
|
return unless valid?(level: level)
|
10
10
|
|
11
11
|
[
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module GS1
|
2
|
+
module Barcode
|
3
|
+
# A segment of a barcode. Retrives a single segment and keeps the rest.
|
4
|
+
#
|
5
|
+
class Segment
|
6
|
+
attr_reader :data, :separator
|
7
|
+
|
8
|
+
def initialize(data, separator: DEFAULT_SEPARATOR)
|
9
|
+
@data = data.chars
|
10
|
+
@separator = separator
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_params
|
14
|
+
return {} unless record
|
15
|
+
|
16
|
+
{ record.underscore_name => record_data }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fetch the two first characters (interpreted as AI) from the remaining
|
20
|
+
# data and try to find record class. If no record class was found, fetch
|
21
|
+
# a third character and try again, and then finally a forth, as no AI
|
22
|
+
# currently have more then 4 characters.
|
23
|
+
def record
|
24
|
+
@record ||= process_ai_variants(2) ||
|
25
|
+
process_ai_variants(1) ||
|
26
|
+
process_ai_variants(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
def record_data
|
30
|
+
return unless record
|
31
|
+
|
32
|
+
@record_data ||= _record_data
|
33
|
+
end
|
34
|
+
|
35
|
+
def rest
|
36
|
+
record_data # Trigger segment retrieval
|
37
|
+
|
38
|
+
@data.join
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?
|
42
|
+
errors.clear
|
43
|
+
|
44
|
+
validate
|
45
|
+
|
46
|
+
errors.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate
|
50
|
+
if record
|
51
|
+
errors << "Unable to retrieve data to #{record}" unless record_data
|
52
|
+
else
|
53
|
+
errors << "Unable to retrieve record from application identifier(s) #{ai_variants.join(', ')}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate!
|
58
|
+
return true if valid?
|
59
|
+
|
60
|
+
raise InvalidTokenError, errors.join(', ')
|
61
|
+
end
|
62
|
+
|
63
|
+
def errors
|
64
|
+
@errors ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def ai_variants
|
70
|
+
@ai_variants ||= []
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_ai_variants(shifts)
|
74
|
+
return unless can_shift_ai_data?(shifts)
|
75
|
+
|
76
|
+
ai_variants << ai_variants.last.to_s + data.shift(shifts).join
|
77
|
+
|
78
|
+
AI_CLASSES[ai_variants.last]
|
79
|
+
end
|
80
|
+
|
81
|
+
def can_shift_ai_data?(shifts)
|
82
|
+
data.size >= shifts
|
83
|
+
end
|
84
|
+
|
85
|
+
def _record_data
|
86
|
+
shift_barcode_length ||
|
87
|
+
shift_separator_length ||
|
88
|
+
shift_max_barcode_length
|
89
|
+
end
|
90
|
+
|
91
|
+
# Record has a fixed barcode length
|
92
|
+
def shift_barcode_length
|
93
|
+
return unless record.barcode_length
|
94
|
+
|
95
|
+
shift_fixed_length(record.barcode_length)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Record has a variable barcode length
|
99
|
+
def shift_separator_length
|
100
|
+
separator_index = data.find_index(separator)
|
101
|
+
|
102
|
+
return unless separator_index && separator_index < record.barcode_max_length
|
103
|
+
|
104
|
+
shift_fixed_length(separator_index).tap do
|
105
|
+
data.shift # Shift separator character
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def shift_max_barcode_length
|
110
|
+
shift_fixed_length(record.barcode_max_length)
|
111
|
+
end
|
112
|
+
|
113
|
+
def shift_fixed_length(shifts)
|
114
|
+
normalized_shifts = [data.size, shifts].min
|
115
|
+
|
116
|
+
return unless can_shift_record_data?(normalized_shifts)
|
117
|
+
|
118
|
+
data.shift(normalized_shifts).join
|
119
|
+
end
|
120
|
+
|
121
|
+
def can_shift_record_data?(shifts)
|
122
|
+
data.size >= shifts && record.allowed_lengths.include?(shifts)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module GS1
|
2
|
+
module Barcode
|
3
|
+
# Class for parsing barcodes. Uses {Segment} for splitting up the individual
|
4
|
+
# parts.
|
5
|
+
#
|
6
|
+
class Tokenizer
|
7
|
+
attr_reader :data, :separator, :params
|
8
|
+
|
9
|
+
def initialize(data, separator: DEFAULT_SEPARATOR)
|
10
|
+
@data = data
|
11
|
+
@separator = separator
|
12
|
+
@params = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_params
|
16
|
+
@to_params ||= segment_to_params(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_params!
|
20
|
+
@to_params ||= segment_to_params(data, true)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def segment_to_params(input, bang = false)
|
26
|
+
segment_from_input(input, bang) do |segment|
|
27
|
+
next if segment.to_params.empty?
|
28
|
+
|
29
|
+
params.merge!(segment.to_params)
|
30
|
+
next if segment.rest.empty?
|
31
|
+
|
32
|
+
segment_to_params(segment.rest, bang)
|
33
|
+
end
|
34
|
+
|
35
|
+
params
|
36
|
+
end
|
37
|
+
|
38
|
+
def segment_from_input(input, bang)
|
39
|
+
Segment.new(input, separator: separator).tap do |segment|
|
40
|
+
segment.validate! if bang
|
41
|
+
yield segment if block_given?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/gs1/barcode.rb
CHANGED
@@ -2,3 +2,13 @@ require 'gs1/barcode/definitions'
|
|
2
2
|
|
3
3
|
require 'gs1/barcode/base'
|
4
4
|
require 'gs1/barcode/healthcare'
|
5
|
+
require 'gs1/barcode/segment'
|
6
|
+
require 'gs1/barcode/tokenizer'
|
7
|
+
|
8
|
+
module GS1
|
9
|
+
module Barcode
|
10
|
+
DEFAULT_SEPARATOR = "\u001E".freeze
|
11
|
+
|
12
|
+
class InvalidTokenError < StandardError; end
|
13
|
+
end
|
14
|
+
end
|
data/lib/gs1/definitions.rb
CHANGED
@@ -2,6 +2,7 @@ module GS1
|
|
2
2
|
# Module for handling definitions.
|
3
3
|
#
|
4
4
|
module Definitions
|
5
|
+
class InvalidDefinitionType < StandardError; end
|
5
6
|
class MissingLengthDefinition < StandardError; end
|
6
7
|
class UnknownDefinition < StandardError; end
|
7
8
|
|
@@ -37,9 +38,9 @@ module GS1
|
|
37
38
|
# Defaults barcode length to allowed length if not explicitly defined, only
|
38
39
|
# if there is one significant allowed.
|
39
40
|
def normalize_length_options(options)
|
40
|
-
options[:allowed] = normalize_multiple_option(options[:allowed])
|
41
|
+
options[:allowed] = normalize_multiple_option(options[:allowed] || options[:barcode])
|
41
42
|
options[:barcode] = normalize_singlural_option(options[:barcode])
|
42
|
-
options[:max_barcode] =
|
43
|
+
options[:max_barcode] = options[:allowed]&.last
|
43
44
|
|
44
45
|
options
|
45
46
|
end
|
@@ -54,10 +55,11 @@ module GS1
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def normalize_singlural_option(option_value)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
return unless option_value
|
59
|
+
|
60
|
+
raise InvalidDefinitionType unless option_value.is_a?(Numeric)
|
61
|
+
|
62
|
+
option_value
|
61
63
|
end
|
62
64
|
|
63
65
|
def barcode_length
|
data/lib/gs1/extensions/gtin.rb
CHANGED
data/lib/gs1/record.rb
CHANGED
data/lib/gs1/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gs1
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Åhman
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-11-
|
11
|
+
date: 2018-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -134,6 +134,8 @@ files:
|
|
134
134
|
- lib/gs1/barcode/base.rb
|
135
135
|
- lib/gs1/barcode/definitions.rb
|
136
136
|
- lib/gs1/barcode/healthcare.rb
|
137
|
+
- lib/gs1/barcode/segment.rb
|
138
|
+
- lib/gs1/barcode/tokenizer.rb
|
137
139
|
- lib/gs1/batch.rb
|
138
140
|
- lib/gs1/check_digit_calculator.rb
|
139
141
|
- lib/gs1/content.rb
|