baldr 0.0.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ module Baldr::Grammar
2
+
3
+ def self.for_version(version)
4
+ case version.length
5
+ when 4
6
+ self.const_get("Version#{version}")
7
+ when 5
8
+ self.const_get("Version#{version[2..4]}0")
9
+ when 6
10
+ self.const_get("Version#{version[2..5]}")
11
+ else
12
+ raise "unknown standard version number: #{version}"
13
+ end
14
+ end
15
+
16
+ end
data/lib/baldr/loop.rb CHANGED
@@ -1,28 +1,26 @@
1
1
  class Baldr::Loop
2
2
 
3
- def initialize(id, builder)
4
- @elements = []
5
- @children = []
6
- @builder = builder
7
- @id = id.upcase.to_s
3
+ attr_reader :id, :segments
4
+
5
+ def initialize(id)
6
+ @segments = []
7
+ @id = id
8
+ end
9
+
10
+ def add(segment)
11
+ @segments << segment
12
+ end
13
+
14
+ def count
15
+ @segments.length
8
16
  end
9
17
 
10
- def method_missing(method, *args, &block)
11
- if method.to_s.start_with?(@id)
12
- element = method.to_s[-2..-1].to_i - 1
13
- @elements[element] = args[0]
14
- else
15
- child = VanillaX12::Segment.new(method, @builder)
16
- child.instance_eval &block if block_given?
17
- @children << child
18
- end
18
+ def number_of_segments
19
+ @segments.map(&:number_of_segments).sum
19
20
  end
20
21
 
21
- def draw
22
- a = [@id] + @elements
23
- s = @builder.separators
24
- result = ["#{a.join(s[:element])}#{s[:segment]}"] + @children.map{ |c| c.draw }
25
- result.join("\n")
22
+ def current_segment
23
+ @segments.last
26
24
  end
27
25
 
28
26
  end
@@ -0,0 +1,107 @@
1
+ class Baldr::Parser
2
+
3
+ attr_reader :error, :envelopes, :separators, :input
4
+
5
+ def initialize(input)
6
+ parse(input)
7
+ end
8
+
9
+ def successful?
10
+ @error.nil?
11
+ end
12
+
13
+ protected
14
+
15
+ def parse(input)
16
+ @input = input
17
+ @separators = detect_separators(input)
18
+ @envelopes = build_tree(split_segments(input, separators))
19
+ @envelopes.each { |e| Baldr::Validator.validate! e }
20
+ self
21
+ rescue Baldr::Error => e
22
+ @error = e.message
23
+ end
24
+
25
+ def detect_separators(input)
26
+ io = StringIO.new(input)
27
+
28
+ isa = io.gets(3)
29
+ raise Baldr::Error, "doesn't begin with ISA..." unless isa == 'ISA'
30
+
31
+ element = io.getbyte
32
+
33
+ 15.times { io.bytes { |b| break if b == element } }
34
+ component = io.getbyte
35
+
36
+ segment = []
37
+ io.bytes do |b|
38
+ break if b.chr =~ /[A-Z]/
39
+ segment << b
40
+ end
41
+
42
+ {
43
+ element: element.chr,
44
+ segment: segment,
45
+ component: component.chr
46
+ }
47
+ end
48
+
49
+ def split_segments(input, separators)
50
+ segments = []
51
+ buffer = []
52
+ skip = 0
53
+
54
+ io = StringIO.new(input)
55
+ io.bytes do |b|
56
+ if skip > 0
57
+ skip -= 1
58
+ else
59
+ if b == separators[:segment].first
60
+ segments << buffer.pack('c*').split(separators[:element])
61
+ skip = separators[:segment].length - 1
62
+ buffer = []
63
+ else
64
+ buffer << b
65
+ end
66
+ end
67
+ end
68
+
69
+ raise Baldr::Error, 'invalid characters in the end of interchange' if skip > 0
70
+
71
+ segments
72
+ end
73
+
74
+ def build_tree(source)
75
+ grammar = Baldr::Grammar::Envelope::STRUCTURE
76
+ loop = build_segment(source.to_enum, grammar, nil)
77
+ loop.segments
78
+ end
79
+
80
+ def build_segment(enumerator, grammar, version)
81
+ current = enumerator.peek
82
+
83
+ while grammar[:id] == current[0]
84
+ loop ||= Baldr::Loop.new(current[0])
85
+ segment = Baldr.const_get((grammar[:class] || :segment).to_s.camelize).new(current[0])
86
+ segment.elements = current[1..-1]
87
+ loop.add segment
88
+
89
+ enumerator.next
90
+
91
+ version ||= segment.sub_version
92
+ sub_grammar = segment.sub_grammar(version) || grammar
93
+ sub_grammar.fetch(:level, []).each do |g|
94
+ child = build_segment(enumerator, g, version)
95
+ segment.children << child if child
96
+ end
97
+
98
+ current = enumerator.peek
99
+ end
100
+
101
+ loop
102
+
103
+ rescue StopIteration
104
+ return loop
105
+ end
106
+
107
+ end
@@ -0,0 +1,16 @@
1
+ module Baldr::Renderer::Json
2
+
3
+ extend self
4
+
5
+ def draw(segment, params = {})
6
+ draw_segment(segment).to_json
7
+ end
8
+
9
+ def draw_segment(segment)
10
+ node = ActiveSupport::OrderedHash.new
11
+ segment.elements.each.with_index { |e, i| node["#{segment.id}#{'%02d' % i}"] = e if e.present? }
12
+ segment.children.each { |loop| node[loop.id] = loop.segments.map { |s| draw_segment(s) } }
13
+ node
14
+ end
15
+
16
+ end
@@ -0,0 +1,27 @@
1
+ module Baldr::Renderer::X12
2
+
3
+ extend self
4
+
5
+ DEFAULT_SEPARATORS = {
6
+ element: '*',
7
+ segment: '~',
8
+ component: '>',
9
+ }.freeze
10
+
11
+ def draw(segment, params = {})
12
+ separators = params[:separators] || DEFAULT_SEPARATORS
13
+ separators[:segment] = separators[:segment].pack('c*') if separators[:segment].is_a?(Array)
14
+ draw_segment(segment, separators).join
15
+ end
16
+
17
+ def draw_segment(segment, separators)
18
+ a = [segment.id] + segment.elements
19
+
20
+ ["#{a.join(separators[:element])}#{separators[:segment]}"] + segment.children.map{ |l| draw_loop(l, separators) }
21
+ end
22
+
23
+ def draw_loop(loop, separators)
24
+ loop.segments.map { |s| draw_segment(s, separators) }
25
+ end
26
+
27
+ end
@@ -0,0 +1,3 @@
1
+ module Baldr::Renderer
2
+
3
+ end
data/lib/baldr/segment.rb CHANGED
@@ -1,28 +1,136 @@
1
1
  class Baldr::Segment
2
2
 
3
- def initialize(id, builder)
3
+ attr_accessor :id, :children, :elements
4
+
5
+ def self.helpers_for_elements(hash)
6
+ hash.each do |element, method|
7
+ self.class_eval <<-RUBY
8
+ def #{method}
9
+ self['#{element}']
10
+ end
11
+
12
+ def #{method}=(value)
13
+ self['#{element}'] = value
14
+ end
15
+ RUBY
16
+ end
17
+ end
18
+
19
+ def initialize(id)
4
20
  @elements = []
5
21
  @children = []
6
- @builder = builder
7
- @id = id.upcase.to_s
22
+ @id = id.to_s.upcase
23
+ end
24
+
25
+ def fetch(key, default = nil)
26
+ self[key] || default
27
+ end
28
+
29
+ def []=(key, value)
30
+ key = key.to_s
31
+ if key.start_with?(@id) && key.length == @id.length + 2
32
+ element = key[-2..-1].to_i - 1
33
+ @elements[element] = value
34
+ elsif !key.start_with?(@id) && key.length > 3 && key =~ /[0-9]{2}$/
35
+ loop = @children.select { |l| l.id == key[0..-3] }.first
36
+ if loop
37
+ if loop.segments.length == 1
38
+ loop.segments.first[key] = value
39
+ else
40
+ raise Buldr::Error, 'there are more than 1 segment in loop. you can\'t assign from here' if loop.segments.length > 1
41
+ end
42
+ else
43
+ raise Buldr::Error, "segment #{key[0..-3]} not found"
44
+ end
45
+ else
46
+ raise Buldr::Error, "unable to assign #{key}"
47
+ end
48
+ end
49
+
50
+ def [](key)
51
+ key = key.to_s
52
+ if key.start_with?(@id) && key.length == @id.length + 2
53
+ element = key[-2..-1].to_i - 1
54
+ @elements[element]
55
+ elsif !key.start_with?(@id) && key.length > 3 && key =~ /[0-9]{2}$/
56
+ loop = @children.select { |l| l.id == key[0..-3] }.first
57
+ if loop
58
+ if loop.segments.length == 1
59
+ loop.segments.first[key]
60
+ else
61
+ raise Buldr::Error, 'there are more than 1 segment in loop. use it as enum' if loop.segments.length > 1
62
+ end
63
+ end
64
+ elsif key =~ /^[A-z][A-Z0-9]{1,2}$/
65
+ loop = @children.select { |l| l.id == key }.first
66
+ if loop
67
+ loop.segments.to_enum
68
+ end
69
+ end
8
70
  end
9
71
 
10
72
  def method_missing(method, *args, &block)
11
- if method.to_s.start_with?(@id)
12
- element = method.to_s[-2..-1].to_i - 1
73
+ m = method.to_s
74
+ if m.start_with?(@id) && m.length == @id.length + 2
75
+ element = m[-2..-1].to_i - 1
13
76
  @elements[element] = args[0]
77
+ elsif m =~ /^[A-Z][A-Z0-9]{1,2}$/
78
+ segment = Baldr::Segment.new(m)
79
+ segment.instance_eval &block if block_given?
80
+ get_loop(m).add segment
14
81
  else
15
- child = VanillaX12::Loop.new(method, @builder)
16
- child.instance_eval &block if block_given?
17
- @children << child
82
+ super
18
83
  end
19
84
  end
20
85
 
21
- def draw
22
- a = [@id] + @elements
23
- s = @builder.separators
24
- result = ["#{a.join(s[:element])}#{s[:segment]}"] + @children.map{ |c| c.draw }
25
- result.join("\n")
86
+ def sub_version
87
+
88
+ end
89
+
90
+ def sub_grammar(version)
91
+
92
+ end
93
+
94
+ def prepare!
95
+
96
+ end
97
+
98
+ def custom_validate!(version)
99
+
100
+ end
101
+
102
+ def number_of_segments
103
+ 1 + @children.map(&:number_of_segments).sum
104
+ end
105
+
106
+ def add(segment)
107
+ get_loop(segment.id).add segment
108
+ end
109
+
110
+ protected
111
+
112
+ def generate_control_number(digits)
113
+ "%0#{digits}d" % Random.rand(10 ** (digits + 1))
114
+ end
115
+
116
+ def get_trailer(trailer_id)
117
+ if @children.last && @children.last.segments.first.id == trailer_id
118
+ trailer = @children.last.segments.first
119
+ else
120
+ trailer = Baldr::Segment.new(trailer_id)
121
+ add trailer
122
+ end
123
+ trailer
124
+ end
125
+
126
+ def get_loop(loop_id)
127
+ if @children.last && @children.last.id.to_s == loop_id
128
+ @children.last
129
+ else
130
+ loop = Baldr::Loop.new(loop_id)
131
+ @children << loop
132
+ loop
133
+ end
26
134
  end
27
135
 
28
136
  end
@@ -0,0 +1,40 @@
1
+ class Baldr::Transaction < Baldr::Segment
2
+
3
+ helpers_for_elements(
4
+ 'ST01' => :transaction_set_code,
5
+ 'ST02' => :transaction_control_number,
6
+ )
7
+
8
+ def initialize(id = 'ST')
9
+ super(id)
10
+ end
11
+
12
+ def prepare!
13
+ trailer = get_trailer('SE')
14
+
15
+ trailer['SE01'] = number_of_segments.to_s
16
+
17
+ self.transaction_control_number ||= generate_control_number(9)
18
+ trailer['SE02'] = transaction_control_number
19
+ end
20
+
21
+ def sub_grammar(version)
22
+ version.for_transaction_set(transaction_set_code)::STRUCTURE
23
+ end
24
+
25
+ def functional_group(version)
26
+ version.for_transaction_set(transaction_set_code)::FUNCTIONAL_GROUP
27
+ end
28
+
29
+ def custom_validate!(version)
30
+ trailer = @children.last.segments.first
31
+ total_number = number_of_segments
32
+ if trailer['SE01'].to_i != total_number
33
+ raise "wrong segments number: #{trailer['SE01']} in SE01, but real number is #{total_number}"
34
+ end
35
+ if trailer['SE02'] != transaction_control_number
36
+ raise "transaction set control numbers don't match: #{trailer['SE02']} in SE02 and #{transaction_control_number} in ST02"
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,93 @@
1
+ module Baldr::Validator
2
+
3
+ extend self
4
+
5
+ def validate!(envelope)
6
+ grammar = Baldr::Grammar::Envelope::STRUCTURE
7
+ record_defs = Baldr::Grammar::Envelope::RECORD_DEFS
8
+ validate_tree!(envelope, grammar, record_defs, nil)
9
+ end
10
+
11
+ protected
12
+
13
+ def validate_tree!(segment, grammar, record_defs, version)
14
+ raise Baldr::Error, "unknown segment #{segment.id}" unless record_defs[segment.id]
15
+
16
+ record_defs[segment.id].each.with_index do |r, i|
17
+ element = segment.elements[i]
18
+ check_required(r, element)
19
+ self.send("check_#{r[:type]}", r, element) unless element.nil?
20
+ end
21
+
22
+ version ||= segment.sub_version
23
+ record_defs = version::RECORD_DEFS if version
24
+ sub_grammar = segment.sub_grammar(version) || grammar
25
+
26
+ l = 0
27
+ sub_grammar.fetch(:level, []).each do |g|
28
+ loop = segment.children[l]
29
+ if loop && loop.id.to_s == g[:id]
30
+ check_loop_count(loop, g)
31
+
32
+ loop.segments.each { |child| validate_tree!(child, g, record_defs, version) }
33
+
34
+ l += 1
35
+ else
36
+ raise Baldr::Error, "segment #{g[:id]} is required, but #{loop.id} was found" if g[:min] > 0
37
+ end
38
+ end
39
+
40
+ segment.custom_validate!(version)
41
+ end
42
+
43
+ def check_loop_count(loop, grammar)
44
+ raise Baldr::Error, "#{loop.id} loop is too long: #{loop.count} segments, maximum #{grammar[:max]}" if loop.count > grammar[:max]
45
+ raise Baldr::Error, "#{loop.id} loop is too short: #{loop.count} segments, minimum #{grammar[:min]}" if loop.count < grammar[:min]
46
+ end
47
+
48
+ def check_required(r, element)
49
+ if r[:required] && (element.nil? || element.size == 0)
50
+ raise Baldr::Error, "element #{r[:id]} is required"
51
+ end
52
+ end
53
+
54
+ def check_string(r, element)
55
+ check_max_and_min_for_string(r, element)
56
+ end
57
+
58
+ def check_id(r, element)
59
+ check_max_and_min_for_string(r, element)
60
+
61
+ end
62
+
63
+ def check_max_and_min_for_string(r, element)
64
+ if r[:max] && element.length > r[:max]
65
+ raise Baldr::Error, "#{r[:id]} is too long: #{element.length} characters, maximum #{r[:max]}"
66
+ end
67
+
68
+ if r[:min] && element.length < r[:min]
69
+ raise Baldr::Error, "#{r[:id]} is too short: #{element.length} characters, minimum #{r[:min]}"
70
+ end
71
+ end
72
+
73
+ def check_time(r, element)
74
+
75
+ end
76
+
77
+ def check_date(r, element)
78
+
79
+ end
80
+
81
+ def check_number(r, element)
82
+
83
+ end
84
+
85
+ def check_real(r, element)
86
+
87
+ end
88
+
89
+ def check_complex(r, element)
90
+
91
+ end
92
+
93
+ end
data/lib/baldr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Baldr
2
- VERSION = '0.0.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/baldr.rb CHANGED
@@ -1 +1,29 @@
1
- require 'baldr/version'
1
+ require 'baldr/version'
2
+
3
+ require 'baldr/builder'
4
+ require 'baldr/parser'
5
+ require 'baldr/segment'
6
+ require 'baldr/envelope'
7
+ require 'baldr/transaction'
8
+ require 'baldr/functional_group'
9
+ require 'baldr/loop'
10
+ require 'baldr/validator'
11
+
12
+ require 'baldr/error'
13
+
14
+ require 'baldr/renderer'
15
+ require 'baldr/renderer/x12'
16
+ require 'baldr/renderer/json'
17
+
18
+ require 'baldr/grammar'
19
+ require 'baldr/grammar/envelope'
20
+ require 'baldr/grammar/version4010'
21
+ require 'baldr/grammar/version4010/set204'
22
+ require 'baldr/grammar/version4010/set210'
23
+ require 'baldr/grammar/version4010/set214'
24
+ require 'baldr/grammar/version4010/set810'
25
+ require 'baldr/grammar/version4010/set850'
26
+ require 'baldr/grammar/version4010/set855'
27
+ require 'baldr/grammar/version4010/set856'
28
+ require 'baldr/grammar/version4010/set990'
29
+ require 'baldr/grammar/version4010/set997'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: baldr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,23 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
  date: 2013-02-06 00:00:00.000000000 Z
13
- dependencies: []
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
14
30
  description: Lightweight EDI X12 translator
15
31
  email: stanislav@spiridonov.pro
16
32
  executables: []
@@ -19,9 +35,29 @@ extra_rdoc_files: []
19
35
  files:
20
36
  - lib/baldr.rb
21
37
  - lib/baldr/builder.rb
22
- - lib/baldr/grammar/set997.rb
38
+ - lib/baldr/envelope.rb
39
+ - lib/baldr/error.rb
40
+ - lib/baldr/functional_group.rb
41
+ - lib/baldr/grammar.rb
42
+ - lib/baldr/grammar/envelope.rb
43
+ - lib/baldr/grammar/version4010.rb
44
+ - lib/baldr/grammar/version4010/set204.rb
45
+ - lib/baldr/grammar/version4010/set210.rb
46
+ - lib/baldr/grammar/version4010/set214.rb
47
+ - lib/baldr/grammar/version4010/set810.rb
48
+ - lib/baldr/grammar/version4010/set850.rb
49
+ - lib/baldr/grammar/version4010/set855.rb
50
+ - lib/baldr/grammar/version4010/set856.rb
51
+ - lib/baldr/grammar/version4010/set990.rb
52
+ - lib/baldr/grammar/version4010/set997.rb
23
53
  - lib/baldr/loop.rb
54
+ - lib/baldr/parser.rb
55
+ - lib/baldr/renderer.rb
56
+ - lib/baldr/renderer/json.rb
57
+ - lib/baldr/renderer/x12.rb
24
58
  - lib/baldr/segment.rb
59
+ - lib/baldr/transaction.rb
60
+ - lib/baldr/validator.rb
25
61
  - lib/baldr/version.rb
26
62
  homepage: https://github.com/spiridonov/baldr
27
63
  licenses: []