edir 0.1.0
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/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +55 -0
- data/Rakefile +21 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/edir/lexer.rb +95 -0
- data/lib/edir/parser.rb +317 -0
- data/lib/edir/parser.y +179 -0
- data/lib/edir/version.rb +5 -0
- data/lib/edir.rb +5 -0
- metadata +62 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 85b647a793b1d14bd9c0eaec8a204f86d1d31c3212f7897029dd3aa7b98ee81a
|
|
4
|
+
data.tar.gz: c76e2aefd32b24c4a9aa2a373734cba02458b6906857a32236d9c58c0ab5252e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d98a8d32a9600808cc50917b845db3560ed646d9cf4c0fbcc0c82d7c58c1ad8b55cf145321882819da0594bd8e474292afbf3602f4ac0233408947c2dc2121b7
|
|
7
|
+
data.tar.gz: 905294a847e34b560fd4fd3a9bfbde47455559af689b1d2d11f4b4f3a09848d48783b239731ca93d883ec8e9be7d35992142dfb2b0ca6e9dc03336f4032072ee
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 2.6
|
|
4
|
+
Exclude:
|
|
5
|
+
- 'lib/edir/parser.rb'
|
|
6
|
+
- 'lib/edir/parser.y'
|
|
7
|
+
- 'vendor/**/*'
|
|
8
|
+
|
|
9
|
+
Style/StringLiterals:
|
|
10
|
+
Enabled: true
|
|
11
|
+
EnforcedStyle: double_quotes
|
|
12
|
+
|
|
13
|
+
Style/StringLiteralsInInterpolation:
|
|
14
|
+
Enabled: true
|
|
15
|
+
EnforcedStyle: double_quotes
|
|
16
|
+
|
|
17
|
+
Layout/LineLength:
|
|
18
|
+
Max: 120
|
|
19
|
+
|
|
20
|
+
Metrics/BlockLength:
|
|
21
|
+
CountComments: false # count full line comments?
|
|
22
|
+
Max: 25
|
|
23
|
+
Exclude:
|
|
24
|
+
- 'Rakefile'
|
|
25
|
+
- '**/*.rake'
|
|
26
|
+
- 'spec/**/*.rb'
|
data/CHANGELOG.md
ADDED
|
File without changes
|
data/Gemfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in ruby-edi.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem "rake", "~> 13.0"
|
|
9
|
+
|
|
10
|
+
gem "rspec", "~> 3.0"
|
|
11
|
+
|
|
12
|
+
gem "rubocop", "~> 1.21"
|
|
13
|
+
|
|
14
|
+
gem "lex", "~> 0.1.0"
|
|
15
|
+
|
|
16
|
+
gem "racc", "~> 1.6.0"
|
|
17
|
+
|
|
18
|
+
group :development, :test do
|
|
19
|
+
gem "pry"
|
|
20
|
+
gem "pry-byebug"
|
|
21
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
edir (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
ast (2.4.2)
|
|
10
|
+
byebug (11.1.3)
|
|
11
|
+
coderay (1.1.3)
|
|
12
|
+
diff-lcs (1.5.0)
|
|
13
|
+
json (2.6.2)
|
|
14
|
+
lex (0.1.0)
|
|
15
|
+
method_source (1.0.0)
|
|
16
|
+
parallel (1.22.1)
|
|
17
|
+
parser (3.1.2.1)
|
|
18
|
+
ast (~> 2.4.1)
|
|
19
|
+
pry (0.14.1)
|
|
20
|
+
coderay (~> 1.1)
|
|
21
|
+
method_source (~> 1.0)
|
|
22
|
+
pry-byebug (3.8.0)
|
|
23
|
+
byebug (~> 11.0)
|
|
24
|
+
pry (~> 0.10)
|
|
25
|
+
racc (1.6.0)
|
|
26
|
+
rainbow (3.1.1)
|
|
27
|
+
rake (13.0.6)
|
|
28
|
+
regexp_parser (2.6.0)
|
|
29
|
+
rexml (3.2.5)
|
|
30
|
+
rspec (3.11.0)
|
|
31
|
+
rspec-core (~> 3.11.0)
|
|
32
|
+
rspec-expectations (~> 3.11.0)
|
|
33
|
+
rspec-mocks (~> 3.11.0)
|
|
34
|
+
rspec-core (3.11.0)
|
|
35
|
+
rspec-support (~> 3.11.0)
|
|
36
|
+
rspec-expectations (3.11.1)
|
|
37
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
38
|
+
rspec-support (~> 3.11.0)
|
|
39
|
+
rspec-mocks (3.11.1)
|
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
41
|
+
rspec-support (~> 3.11.0)
|
|
42
|
+
rspec-support (3.11.1)
|
|
43
|
+
rubocop (1.36.0)
|
|
44
|
+
json (~> 2.3)
|
|
45
|
+
parallel (~> 1.10)
|
|
46
|
+
parser (>= 3.1.2.1)
|
|
47
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
48
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
49
|
+
rexml (>= 3.2.5, < 4.0)
|
|
50
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
|
51
|
+
ruby-progressbar (~> 1.7)
|
|
52
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
53
|
+
rubocop-ast (1.21.0)
|
|
54
|
+
parser (>= 3.1.1.0)
|
|
55
|
+
ruby-progressbar (1.11.0)
|
|
56
|
+
unicode-display_width (2.3.0)
|
|
57
|
+
|
|
58
|
+
PLATFORMS
|
|
59
|
+
ruby
|
|
60
|
+
universal-darwin-21
|
|
61
|
+
x86_64-linux
|
|
62
|
+
|
|
63
|
+
DEPENDENCIES
|
|
64
|
+
edir!
|
|
65
|
+
lex (~> 0.1.0)
|
|
66
|
+
pry
|
|
67
|
+
pry-byebug
|
|
68
|
+
racc (~> 1.6.0)
|
|
69
|
+
rake (~> 13.0)
|
|
70
|
+
rspec (~> 3.0)
|
|
71
|
+
rubocop (~> 1.21)
|
|
72
|
+
|
|
73
|
+
BUNDLED WITH
|
|
74
|
+
2.2.32
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Kaleb McKone
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Edir
|
|
2
|
+
|
|
3
|
+
This is a Gem for parsing files. Right now there's no configuration and offers essentially 1 feature: reading EDI and outputting it as formatted Ruby data.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'edir'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install edir
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Right now there's not a lot to it. Use the parser like so:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require 'edir'
|
|
27
|
+
|
|
28
|
+
edi_data = Edir::Parser.new.parse(edi_string)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Example
|
|
32
|
+
```ruby
|
|
33
|
+
Edir::Parser.new.parse(File.read('spec/fixtures/files/204_example_1.txt'))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The lexer is implemented in pure ruby, so changes in `lexer.rb` are always current. However,
|
|
37
|
+
the parser is converted from a racc/yacc specification to pure ruby. To rebuild the parser, run
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
bundle exec rake parser[_debug]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
46
|
+
|
|
47
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
48
|
+
|
|
49
|
+
## Contributing
|
|
50
|
+
|
|
51
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/edir.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
require "rubocop/rake_task"
|
|
9
|
+
|
|
10
|
+
RuboCop::RakeTask.new
|
|
11
|
+
|
|
12
|
+
task default: %i[spec rubocop]
|
|
13
|
+
|
|
14
|
+
desc "Generate Parser"
|
|
15
|
+
task :parser do
|
|
16
|
+
`racc lib/edir/parser.y -o lib/edir/parser.rb`
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
task :parser_debug do
|
|
20
|
+
`racc -g lib/edir/parser.y -o lib/edir/parser.rb`
|
|
21
|
+
end
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "edir"
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require "irb"
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/edir/lexer.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lex"
|
|
4
|
+
|
|
5
|
+
module Edir
|
|
6
|
+
# Top level API class for providing EDI lexing
|
|
7
|
+
class Lexer < Lex::Lexer
|
|
8
|
+
tokens(
|
|
9
|
+
:SEGSTART,
|
|
10
|
+
:SEGEND,
|
|
11
|
+
:ELEMSEP,
|
|
12
|
+
:ELEM
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
states(
|
|
16
|
+
insegment: :exclusive,
|
|
17
|
+
inelement: :exclusive
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
rule(:SEGSTART, /\w{2,3}(?=[*|~])/) do |lexer, token|
|
|
21
|
+
lexer.push_state(:insegment)
|
|
22
|
+
token
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Rule to match when we have started a segment
|
|
26
|
+
# but it has no elements
|
|
27
|
+
rule(:insegment_SEGEND, /~|\n/) do |lexer, token|
|
|
28
|
+
lexer.pop_state
|
|
29
|
+
token
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
rule(:insegment_ELEMSEP, /\*|\|/) do |lexer, token|
|
|
33
|
+
# If we are insegment, when we encounter an element separator it means
|
|
34
|
+
# we should now be inelement. Otherwise, we were inelement and should
|
|
35
|
+
# leave the state, back to insegment.
|
|
36
|
+
if lexer.current_state == :insegment
|
|
37
|
+
lexer.push_state(:inelement)
|
|
38
|
+
else
|
|
39
|
+
lexer.pop_state
|
|
40
|
+
end
|
|
41
|
+
token
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
rule(:inelement_ELEMSEP, /\*|\|/)
|
|
45
|
+
|
|
46
|
+
rule(:inelement_ELEM, /[\w\s\-().,!:@>'^]+/) do |_lexer, token|
|
|
47
|
+
token.value = token.value.strip
|
|
48
|
+
token
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
rule(:inelement_SEGEND, /~|\n/) do |lexer, token|
|
|
52
|
+
lexer.pop_state
|
|
53
|
+
lexer.pop_state
|
|
54
|
+
token
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
ignore "\n"
|
|
58
|
+
ignore :insegment, "\s"
|
|
59
|
+
ignore :inelement, "\s"
|
|
60
|
+
|
|
61
|
+
error :insegment do |lexer, token|
|
|
62
|
+
error_handler(lexer, token)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
error :inelement do |lexer, token|
|
|
66
|
+
error_handler(lexer, token)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def lex_str(str)
|
|
70
|
+
# NOTE: Need to decide if this is really what we want to do here.
|
|
71
|
+
# Do the token objects contain any more useful information we
|
|
72
|
+
# should be passing on?
|
|
73
|
+
with_empty_elements_added = []
|
|
74
|
+
tokens = lex(str).map { |o| [o.name, o.value] }
|
|
75
|
+
# TODO: There's got to be a better way to do this
|
|
76
|
+
# I just want to know if two adjacent tokens are :ELEMSEP so we can
|
|
77
|
+
# add back the empty element that isn't easily handled by the lexer
|
|
78
|
+
tokens.each_with_index.map do |token, i|
|
|
79
|
+
with_empty_elements_added << token
|
|
80
|
+
if token.first == :ELEMSEP && i + 1 < tokens.length && tokens[i + 1].first == :ELEMSEP
|
|
81
|
+
with_empty_elements_added << [:ELEM, ""]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
with_empty_elements_added
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class << self
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def error_handler(_lexer, token)
|
|
91
|
+
puts "Illegal character #{token.value}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/edir/parser.rb
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DO NOT MODIFY!!!!
|
|
3
|
+
# This file is automatically generated by Racc 1.6.0
|
|
4
|
+
# from Racc grammar file "".
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
require 'racc/parser.rb'
|
|
8
|
+
|
|
9
|
+
require_relative 'lexer'
|
|
10
|
+
|
|
11
|
+
class Edir::Interchange
|
|
12
|
+
attr_reader :func_groups
|
|
13
|
+
def initialize(header:, footer:, func_groups:)
|
|
14
|
+
@header = header
|
|
15
|
+
@footer = footer
|
|
16
|
+
@func_groups = func_groups
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def segments
|
|
20
|
+
[@header] + @func_groups.map(&:segments).flatten + [@footer]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def elements
|
|
24
|
+
segments.map(&:elements)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Edir::FunctionalGroup
|
|
29
|
+
attr_reader :transac_sets
|
|
30
|
+
|
|
31
|
+
def initialize(header:, footer:, transac_sets:)
|
|
32
|
+
@header = header
|
|
33
|
+
@footer = footer
|
|
34
|
+
@transac_sets = transac_sets
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def segments
|
|
38
|
+
[@header] + @transac_sets.map(&:segments) + [@footer]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def elements
|
|
42
|
+
segments.map(&:elements)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Edir::TransactionSet
|
|
47
|
+
def initialize(header:, footer:, segments:)
|
|
48
|
+
@header = header
|
|
49
|
+
@footer = footer
|
|
50
|
+
@segments = segments
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def segments
|
|
54
|
+
[@header] + @segments + [@footer]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def elements
|
|
58
|
+
segments.map(&:elements)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Edir::Segment
|
|
63
|
+
attr_reader :elements
|
|
64
|
+
attr_reader :name
|
|
65
|
+
attr_reader :raw_data
|
|
66
|
+
|
|
67
|
+
def initialize(data)
|
|
68
|
+
@raw_data = data
|
|
69
|
+
|
|
70
|
+
@name = data.first
|
|
71
|
+
@elements = []
|
|
72
|
+
position = 0
|
|
73
|
+
@raw_data[1..].each do |element|
|
|
74
|
+
if element =~ /[*|]/
|
|
75
|
+
position += 1
|
|
76
|
+
else
|
|
77
|
+
@elements.push([element, position])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def get_element(position)
|
|
83
|
+
@elements.detect { |e| e[1] == position}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module Edir
|
|
88
|
+
class Parser < Racc::Parser
|
|
89
|
+
|
|
90
|
+
module_eval(<<'...end parser.y/module_eval...', 'parser.y', 99)
|
|
91
|
+
def initialize(debug: false)
|
|
92
|
+
@yydebug = debug
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse(str)
|
|
96
|
+
@q = Edir::Lexer.new.lex_str(str)
|
|
97
|
+
data = do_parse
|
|
98
|
+
# Implicit segment vs document mode
|
|
99
|
+
if data.length > 1
|
|
100
|
+
convert_document(data)
|
|
101
|
+
else
|
|
102
|
+
data
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def next_token
|
|
107
|
+
@q.shift
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# For each transaction set start/end, create a unique transaction set object with
|
|
111
|
+
# the corrsponding segments.
|
|
112
|
+
# For each functional group start/end, create a unique functional group object with
|
|
113
|
+
# the corresponding transaction sets.
|
|
114
|
+
# For each interchange start/end, create a unique interchange object with the corresponding
|
|
115
|
+
# functional groups.
|
|
116
|
+
def convert_document(segments)
|
|
117
|
+
interchanges = partition_by_seg_types(segments: segments, seg_start: "ISA", seg_end: "IEA")
|
|
118
|
+
interchanges.map do |inter|
|
|
119
|
+
convert_interchange(inter)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def convert_interchange(inter)
|
|
124
|
+
func_groups = partition_by_seg_types(segments: inter[1..-2], seg_start: "GS", seg_end: "GE")
|
|
125
|
+
converted_func_groups = func_groups.map do |func_group|
|
|
126
|
+
convert_functional_group(func_group)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
Edir::Interchange.new(
|
|
130
|
+
header: inter.first,
|
|
131
|
+
footer: inter.last,
|
|
132
|
+
func_groups: converted_func_groups
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def convert_functional_group(func_group)
|
|
137
|
+
transac_sets = partition_by_seg_types(segments: func_group[1..-2], seg_start: "ST", seg_end: "SE")
|
|
138
|
+
converted_transac_sets = transac_sets.map do |transac_set|
|
|
139
|
+
convert_transaction_set(transac_set)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
Edir::FunctionalGroup.new(
|
|
143
|
+
header: func_group.first,
|
|
144
|
+
footer: func_group.last,
|
|
145
|
+
transac_sets: converted_transac_sets
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def convert_transaction_set(transac_set)
|
|
150
|
+
Edir::TransactionSet.new(
|
|
151
|
+
header: transac_set.first,
|
|
152
|
+
footer: transac_set.last,
|
|
153
|
+
segments: transac_set[1..-2]
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def partition_by_seg_types(segments:, seg_start:, seg_end:)
|
|
158
|
+
partitions = []
|
|
159
|
+
while segments.length > 0 do
|
|
160
|
+
part_start = segments.find_index do |segment|
|
|
161
|
+
segment.name == seg_start
|
|
162
|
+
end
|
|
163
|
+
part_end = segments.find_index do |segment|
|
|
164
|
+
segment.name == seg_end
|
|
165
|
+
end
|
|
166
|
+
partitions << segments[part_start..part_end]
|
|
167
|
+
segments = segments[part_end+1..]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
partitions
|
|
171
|
+
end
|
|
172
|
+
...end parser.y/module_eval...
|
|
173
|
+
##### State transition tables begin ###
|
|
174
|
+
|
|
175
|
+
racc_action_table = [
|
|
176
|
+
7, 8, 9, 8, 9, 8, 9, 3, 4, 3,
|
|
177
|
+
10, 11 ]
|
|
178
|
+
|
|
179
|
+
racc_action_check = [
|
|
180
|
+
3, 3, 3, 8, 8, 9, 9, 0, 1, 2,
|
|
181
|
+
4, 6 ]
|
|
182
|
+
|
|
183
|
+
racc_action_pointer = [
|
|
184
|
+
5, 8, 7, -3, 10, nil, 8, nil, -1, 1,
|
|
185
|
+
nil, nil, nil, nil ]
|
|
186
|
+
|
|
187
|
+
racc_action_default = [
|
|
188
|
+
-8, -8, -2, -8, -8, -1, -8, -4, -8, -7,
|
|
189
|
+
14, -3, -5, -6 ]
|
|
190
|
+
|
|
191
|
+
racc_goto_table = [
|
|
192
|
+
6, 1, nil, 5, nil, 12, 13 ]
|
|
193
|
+
|
|
194
|
+
racc_goto_check = [
|
|
195
|
+
3, 1, nil, 1, nil, 3, 3 ]
|
|
196
|
+
|
|
197
|
+
racc_goto_pointer = [
|
|
198
|
+
nil, 1, nil, -3 ]
|
|
199
|
+
|
|
200
|
+
racc_goto_default = [
|
|
201
|
+
nil, nil, 2, nil ]
|
|
202
|
+
|
|
203
|
+
racc_reduce_table = [
|
|
204
|
+
0, 0, :racc_error,
|
|
205
|
+
2, 7, :_reduce_1,
|
|
206
|
+
1, 7, :_reduce_2,
|
|
207
|
+
3, 8, :_reduce_3,
|
|
208
|
+
2, 8, :_reduce_4,
|
|
209
|
+
2, 9, :_reduce_5,
|
|
210
|
+
2, 9, :_reduce_6,
|
|
211
|
+
1, 9, :_reduce_7 ]
|
|
212
|
+
|
|
213
|
+
racc_reduce_n = 8
|
|
214
|
+
|
|
215
|
+
racc_shift_n = 14
|
|
216
|
+
|
|
217
|
+
racc_token_table = {
|
|
218
|
+
false => 0,
|
|
219
|
+
:error => 1,
|
|
220
|
+
:SEGSTART => 2,
|
|
221
|
+
:SEGEND => 3,
|
|
222
|
+
:ELEMSEP => 4,
|
|
223
|
+
:ELEM => 5 }
|
|
224
|
+
|
|
225
|
+
racc_nt_base = 6
|
|
226
|
+
|
|
227
|
+
racc_use_result_var = true
|
|
228
|
+
|
|
229
|
+
Racc_arg = [
|
|
230
|
+
racc_action_table,
|
|
231
|
+
racc_action_check,
|
|
232
|
+
racc_action_default,
|
|
233
|
+
racc_action_pointer,
|
|
234
|
+
racc_goto_table,
|
|
235
|
+
racc_goto_check,
|
|
236
|
+
racc_goto_default,
|
|
237
|
+
racc_goto_pointer,
|
|
238
|
+
racc_nt_base,
|
|
239
|
+
racc_reduce_table,
|
|
240
|
+
racc_token_table,
|
|
241
|
+
racc_shift_n,
|
|
242
|
+
racc_reduce_n,
|
|
243
|
+
racc_use_result_var ]
|
|
244
|
+
|
|
245
|
+
Racc_token_to_s_table = [
|
|
246
|
+
"$end",
|
|
247
|
+
"error",
|
|
248
|
+
"SEGSTART",
|
|
249
|
+
"SEGEND",
|
|
250
|
+
"ELEMSEP",
|
|
251
|
+
"ELEM",
|
|
252
|
+
"$start",
|
|
253
|
+
"segments",
|
|
254
|
+
"segment",
|
|
255
|
+
"elems" ]
|
|
256
|
+
|
|
257
|
+
Racc_debug_parser = false
|
|
258
|
+
|
|
259
|
+
##### State transition tables end #####
|
|
260
|
+
|
|
261
|
+
# reduce 0 omitted
|
|
262
|
+
|
|
263
|
+
module_eval(<<'.,.,', 'parser.y', 9)
|
|
264
|
+
def _reduce_1(val, _values, result)
|
|
265
|
+
result = [val[0]] + val[1]
|
|
266
|
+
result
|
|
267
|
+
end
|
|
268
|
+
.,.,
|
|
269
|
+
|
|
270
|
+
module_eval(<<'.,.,', 'parser.y', 10)
|
|
271
|
+
def _reduce_2(val, _values, result)
|
|
272
|
+
result = val
|
|
273
|
+
result
|
|
274
|
+
end
|
|
275
|
+
.,.,
|
|
276
|
+
|
|
277
|
+
module_eval(<<'.,.,', 'parser.y', 11)
|
|
278
|
+
def _reduce_3(val, _values, result)
|
|
279
|
+
result = Edir::Segment.new([val[0]] + val[1])
|
|
280
|
+
result
|
|
281
|
+
end
|
|
282
|
+
.,.,
|
|
283
|
+
|
|
284
|
+
module_eval(<<'.,.,', 'parser.y', 12)
|
|
285
|
+
def _reduce_4(val, _values, result)
|
|
286
|
+
result = Edir::Segment.new([val[0]])
|
|
287
|
+
result
|
|
288
|
+
end
|
|
289
|
+
.,.,
|
|
290
|
+
|
|
291
|
+
module_eval(<<'.,.,', 'parser.y', 13)
|
|
292
|
+
def _reduce_5(val, _values, result)
|
|
293
|
+
result = [val[0]] + val[1]
|
|
294
|
+
result
|
|
295
|
+
end
|
|
296
|
+
.,.,
|
|
297
|
+
|
|
298
|
+
module_eval(<<'.,.,', 'parser.y', 14)
|
|
299
|
+
def _reduce_6(val, _values, result)
|
|
300
|
+
result = [val[0]] + val[1]
|
|
301
|
+
result
|
|
302
|
+
end
|
|
303
|
+
.,.,
|
|
304
|
+
|
|
305
|
+
module_eval(<<'.,.,', 'parser.y', 15)
|
|
306
|
+
def _reduce_7(val, _values, result)
|
|
307
|
+
result = val
|
|
308
|
+
result
|
|
309
|
+
end
|
|
310
|
+
.,.,
|
|
311
|
+
|
|
312
|
+
def _reduce_none(val, _values, result)
|
|
313
|
+
val[0]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
end # class Parser
|
|
317
|
+
end # module Edir
|
data/lib/edir/parser.y
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
class Edir::Parser
|
|
2
|
+
token SEGSTART SEGEND ELEMSEP ELEM
|
|
3
|
+
# local variables that racc provides in the environment of action:
|
|
4
|
+
# * val is the right hand side
|
|
5
|
+
# * result is the left hand side
|
|
6
|
+
# The plural definitions return an array while the singular ones
|
|
7
|
+
# return a single element. In order to build a sensibly flat representation of the
|
|
8
|
+
# document, we concatenate the single elements with the ones that return an array
|
|
9
|
+
rule
|
|
10
|
+
segments : segment segments { result = [val[0]] + val[1] }
|
|
11
|
+
| segment { result = val }
|
|
12
|
+
segment : SEGSTART elems SEGEND { result = Edir::Segment.new([val[0]] + val[1]) }
|
|
13
|
+
| SEGSTART SEGEND { result = Edir::Segment.new([val[0]]) }
|
|
14
|
+
elems : ELEMSEP elems { result = [val[0]] + val[1] }
|
|
15
|
+
| ELEM elems { result = [val[0]] + val[1] }
|
|
16
|
+
| ELEM { result = val }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
---- header
|
|
20
|
+
require_relative 'lexer'
|
|
21
|
+
|
|
22
|
+
class Edir::Interchange
|
|
23
|
+
attr_reader :func_groups
|
|
24
|
+
def initialize(header:, footer:, func_groups:)
|
|
25
|
+
@header = header
|
|
26
|
+
@footer = footer
|
|
27
|
+
@func_groups = func_groups
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def segments
|
|
31
|
+
[@header] + @func_groups.map(&:segments).flatten + [@footer]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def elements
|
|
35
|
+
segments.map(&:elements)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Edir::FunctionalGroup
|
|
40
|
+
attr_reader :transac_sets
|
|
41
|
+
|
|
42
|
+
def initialize(header:, footer:, transac_sets:)
|
|
43
|
+
@header = header
|
|
44
|
+
@footer = footer
|
|
45
|
+
@transac_sets = transac_sets
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def segments
|
|
49
|
+
[@header] + @transac_sets.map(&:segments) + [@footer]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def elements
|
|
53
|
+
segments.map(&:elements)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Edir::TransactionSet
|
|
58
|
+
def initialize(header:, footer:, segments:)
|
|
59
|
+
@header = header
|
|
60
|
+
@footer = footer
|
|
61
|
+
@segments = segments
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def segments
|
|
65
|
+
[@header] + @segments + [@footer]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def elements
|
|
69
|
+
segments.map(&:elements)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class Edir::Segment
|
|
74
|
+
attr_reader :elements
|
|
75
|
+
attr_reader :name
|
|
76
|
+
attr_reader :raw_data
|
|
77
|
+
|
|
78
|
+
def initialize(data)
|
|
79
|
+
@raw_data = data
|
|
80
|
+
|
|
81
|
+
@name = data.first
|
|
82
|
+
@elements = []
|
|
83
|
+
position = 0
|
|
84
|
+
@raw_data[1..].each do |element|
|
|
85
|
+
if element =~ /[*|]/
|
|
86
|
+
position += 1
|
|
87
|
+
else
|
|
88
|
+
@elements.push([element, position])
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def get_element(position)
|
|
94
|
+
@elements.detect { |e| e[1] == position}
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
---- inner
|
|
99
|
+
def initialize(debug: false)
|
|
100
|
+
@yydebug = debug
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse(str)
|
|
104
|
+
@q = Edir::Lexer.new.lex_str(str)
|
|
105
|
+
data = do_parse
|
|
106
|
+
# Implicit segment vs document mode
|
|
107
|
+
if data.length > 1
|
|
108
|
+
convert_document(data)
|
|
109
|
+
else
|
|
110
|
+
data
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def next_token
|
|
115
|
+
@q.shift
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# For each transaction set start/end, create a unique transaction set object with
|
|
119
|
+
# the corrsponding segments.
|
|
120
|
+
# For each functional group start/end, create a unique functional group object with
|
|
121
|
+
# the corresponding transaction sets.
|
|
122
|
+
# For each interchange start/end, create a unique interchange object with the corresponding
|
|
123
|
+
# functional groups.
|
|
124
|
+
def convert_document(segments)
|
|
125
|
+
interchanges = partition_by_seg_types(segments: segments, seg_start: "ISA", seg_end: "IEA")
|
|
126
|
+
interchanges.map do |inter|
|
|
127
|
+
convert_interchange(inter)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def convert_interchange(inter)
|
|
132
|
+
func_groups = partition_by_seg_types(segments: inter[1..-2], seg_start: "GS", seg_end: "GE")
|
|
133
|
+
converted_func_groups = func_groups.map do |func_group|
|
|
134
|
+
convert_functional_group(func_group)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
Edir::Interchange.new(
|
|
138
|
+
header: inter.first,
|
|
139
|
+
footer: inter.last,
|
|
140
|
+
func_groups: converted_func_groups
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def convert_functional_group(func_group)
|
|
145
|
+
transac_sets = partition_by_seg_types(segments: func_group[1..-2], seg_start: "ST", seg_end: "SE")
|
|
146
|
+
converted_transac_sets = transac_sets.map do |transac_set|
|
|
147
|
+
convert_transaction_set(transac_set)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
Edir::FunctionalGroup.new(
|
|
151
|
+
header: func_group.first,
|
|
152
|
+
footer: func_group.last,
|
|
153
|
+
transac_sets: converted_transac_sets
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def convert_transaction_set(transac_set)
|
|
158
|
+
Edir::TransactionSet.new(
|
|
159
|
+
header: transac_set.first,
|
|
160
|
+
footer: transac_set.last,
|
|
161
|
+
segments: transac_set[1..-2]
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def partition_by_seg_types(segments:, seg_start:, seg_end:)
|
|
166
|
+
partitions = []
|
|
167
|
+
while segments.length > 0 do
|
|
168
|
+
part_start = segments.find_index do |segment|
|
|
169
|
+
segment.name == seg_start
|
|
170
|
+
end
|
|
171
|
+
part_end = segments.find_index do |segment|
|
|
172
|
+
segment.name == seg_end
|
|
173
|
+
end
|
|
174
|
+
partitions << segments[part_start..part_end]
|
|
175
|
+
segments = segments[part_end+1..]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
partitions
|
|
179
|
+
end
|
data/lib/edir/version.rb
ADDED
data/lib/edir.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: edir
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaleb McKone
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-03-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
- 20476319+krmckone@users.noreply.github.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".rspec"
|
|
21
|
+
- ".rubocop.yml"
|
|
22
|
+
- CHANGELOG.md
|
|
23
|
+
- Gemfile
|
|
24
|
+
- Gemfile.lock
|
|
25
|
+
- LICENSE.txt
|
|
26
|
+
- README.md
|
|
27
|
+
- Rakefile
|
|
28
|
+
- bin/console
|
|
29
|
+
- bin/setup
|
|
30
|
+
- lib/edir.rb
|
|
31
|
+
- lib/edir/lexer.rb
|
|
32
|
+
- lib/edir/parser.rb
|
|
33
|
+
- lib/edir/parser.y
|
|
34
|
+
- lib/edir/version.rb
|
|
35
|
+
homepage: https://github.com/krmckone/edir
|
|
36
|
+
licenses:
|
|
37
|
+
- MIT
|
|
38
|
+
metadata:
|
|
39
|
+
homepage_uri: https://github.com/krmckone/edir
|
|
40
|
+
source_code_uri: https://github.com/krmckone/edir
|
|
41
|
+
changelog_uri: https://github.com/krmckone/edir/CHANGELOG.md
|
|
42
|
+
rubygems_mfa_required: 'true'
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 2.6.0
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.2.27
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: EDI File parsing in ruby
|
|
62
|
+
test_files: []
|