edi4r 0.9.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/AuthorCopyright +10 -0
- data/COPYING +56 -0
- data/ChangeLog +106 -0
- data/README +66 -0
- data/TO-DO +35 -0
- data/Tutorial +609 -0
- data/VERSION +1 -0
- data/bin/edi2xml.rb +103 -0
- data/bin/editool.rb +151 -0
- data/bin/xml2edi.rb +50 -0
- data/data/edifact/iso9735/SDCD.10000.csv +10 -0
- data/data/edifact/iso9735/SDCD.20000.csv +10 -0
- data/data/edifact/iso9735/SDCD.30000.csv +11 -0
- data/data/edifact/iso9735/SDCD.40000.csv +31 -0
- data/data/edifact/iso9735/SDCD.40100.csv +31 -0
- data/data/edifact/iso9735/SDED.10000.csv +37 -0
- data/data/edifact/iso9735/SDED.20000.csv +37 -0
- data/data/edifact/iso9735/SDED.30000.csv +43 -0
- data/data/edifact/iso9735/SDED.40000.csv +129 -0
- data/data/edifact/iso9735/SDED.40100.csv +130 -0
- data/data/edifact/iso9735/SDMD.10000.csv +0 -0
- data/data/edifact/iso9735/SDMD.20000.csv +0 -0
- data/data/edifact/iso9735/SDMD.30000.csv +6 -0
- data/data/edifact/iso9735/SDMD.40000.csv +17 -0
- data/data/edifact/iso9735/SDMD.40100.csv +17 -0
- data/data/edifact/iso9735/SDSD.10000.csv +8 -0
- data/data/edifact/iso9735/SDSD.20000.csv +8 -0
- data/data/edifact/iso9735/SDSD.30000.csv +12 -0
- data/data/edifact/iso9735/SDSD.40000.csv +34 -0
- data/data/edifact/iso9735/SDSD.40100.csv +34 -0
- data/data/edifact/untdid/EDCD.d01b.csv +200 -0
- data/data/edifact/untdid/EDCD.d96a.csv +161 -0
- data/data/edifact/untdid/EDED.d01b.csv +641 -0
- data/data/edifact/untdid/EDED.d96a.csv +462 -0
- data/data/edifact/untdid/EDMD.d01b.csv +3419 -0
- data/data/edifact/untdid/EDMD.d96a.csv +2144 -0
- data/data/edifact/untdid/EDSD.d01b.csv +158 -0
- data/data/edifact/untdid/EDSD.d96a.csv +127 -0
- data/data/edifact/untdid/IDCD.d01b.csv +95 -0
- data/data/edifact/untdid/IDMD.d01b.csv +238 -0
- data/data/edifact/untdid/IDSD.d01b.csv +75 -0
- data/lib/edi4r.rb +928 -0
- data/lib/edi4r/diagrams.rb +567 -0
- data/lib/edi4r/edi4r-1.2.dtd +20 -0
- data/lib/edi4r/edifact-rexml.rb +221 -0
- data/lib/edi4r/edifact.rb +1627 -0
- data/lib/edi4r/rexml.rb +256 -0
- data/lib/edi4r/standards.rb +495 -0
- data/test/eancom2webedi.rb +380 -0
- data/test/groups.edi +1 -0
- data/test/in1.edi +1 -0
- data/test/in1.inh +3 -0
- data/test/in2.edi +1 -0
- data/test/in2.xml +350 -0
- data/test/test_basics.rb +209 -0
- data/test/test_edi_split.rb +53 -0
- data/test/test_loopback.rb +21 -0
- data/test/test_minidemo.rb +84 -0
- data/test/test_rexml.rb +98 -0
- data/test/test_tut_examples.rb +131 -0
- data/test/webedi2eancom.rb +408 -0
- metadata +110 -0
data/AuthorCopyright
ADDED
data/COPYING
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see the file GPL), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) give non-standard binaries non-standard names, with
|
21
|
+
instructions on where to get the original software distribution.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or binary form,
|
26
|
+
provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the binaries and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard binaries non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under these terms.
|
43
|
+
|
44
|
+
For the list of those files and their copying conditions, see the
|
45
|
+
file LEGAL.
|
46
|
+
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
48
|
+
output from the software do not automatically fall under the
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
51
|
+
software.
|
52
|
+
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
56
|
+
PURPOSE.
|
data/ChangeLog
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
= Change log
|
2
|
+
|
3
|
+
== 0.9.4.1
|
4
|
+
|
5
|
+
=== News
|
6
|
+
* XML support added (again; no separate module anymore)!
|
7
|
+
* DIN 16557-4 now supported (one-way, EDI-to-XML)
|
8
|
+
* New standalone tools: edi2xml.rb, xml2edi.rb
|
9
|
+
* New testcase: test_rexml.rb
|
10
|
+
* EDI::Interchange: New convenience methods: parse(), peek(), detect()
|
11
|
+
* Support for compressed data (gzip via zlib, bzip2 if "bzcat" available)
|
12
|
+
* peek(): High-speed access to (just the) Interchange header data
|
13
|
+
|
14
|
+
=== Changes
|
15
|
+
* Some optional calling parameters added
|
16
|
+
* editool.rb: Now supporting more standards, compression, and peek mode/reports
|
17
|
+
|
18
|
+
=== Misc improvements & bug fixes
|
19
|
+
* Bug in EDIFACT scanner fixed - scanner re-implemented
|
20
|
+
* Windows supported now (workaround for issue with Pathname#realpath).
|
21
|
+
Note: Put "ruby" in front of edi4r scripts & tools when invoked
|
22
|
+
in pipe mode in a DOS shell, or the pipe fill fail.
|
23
|
+
|
24
|
+
== 0.9.4.0
|
25
|
+
=== New structures
|
26
|
+
* Time: Class method "edifact", method "format" added
|
27
|
+
* MsgGroup: Now fully supported
|
28
|
+
|
29
|
+
=== Changes
|
30
|
+
* New test case "test_minidemo" aimed at fixes in DE#to_s, see below
|
31
|
+
* "test_basics" now covering MsgGroup tests as well
|
32
|
+
* Special setters de0020=, de0048=, de0062=, de0340= added
|
33
|
+
to make header/trailer changes consistent.
|
34
|
+
* Setters de0001=, de0002= added to prevent changes to charset and version
|
35
|
+
* E::Message#validate: Improved
|
36
|
+
* Tests updated to reflect new features
|
37
|
+
|
38
|
+
=== Misc improvements & bug fixes
|
39
|
+
* E::Interchange.parse: hnd.close removed; some code cleanup
|
40
|
+
* EDI::MsgGroup: Completed
|
41
|
+
* EDI::E::MsgGroup: Completed, now fully supported
|
42
|
+
* E::DE#to_s: Fix - now calling super()
|
43
|
+
* DE#to_s: Fix - adding leading zeroes for too short numeric values
|
44
|
+
* DE#validate: No more warnings if format e.g. n6 and fixable
|
45
|
+
* E::Illegal_Charset_Patterns: Bugfix ('+' missing, '*' included twice)
|
46
|
+
|
47
|
+
|
48
|
+
== 0.9.3.1
|
49
|
+
Bugfix release, now integrated in 0.9.4
|
50
|
+
|
51
|
+
=== New structures
|
52
|
+
* (none so far)
|
53
|
+
|
54
|
+
=== Changes
|
55
|
+
* New test case "test_minidemo" aimed at fixes in DE#to_s, see below
|
56
|
+
|
57
|
+
=== Misc improvements & bug fixes
|
58
|
+
* E::Interchange.parse: hnd.close removed; some code cleanup
|
59
|
+
* E::DE#to_s: Fix - now calling super()
|
60
|
+
* DE#to_s: Fix - adding leading zeroes for too short numeric values
|
61
|
+
* DE#validate: No more warnings if format e.g. n6 and fixable
|
62
|
+
|
63
|
+
|
64
|
+
== 0.9.3
|
65
|
+
=== New structures
|
66
|
+
* Improved class hierarchy
|
67
|
+
* Methods: More consistent, better inheritance
|
68
|
+
* Removed: write() (use to_s !)
|
69
|
+
* inspect(): more attributes for segments
|
70
|
+
* EDI::E::UNA now bundles all UNA related methods
|
71
|
+
|
72
|
+
=== Changes
|
73
|
+
* New access to DE/CDE arrays (like ) through prefix 'a'
|
74
|
+
prevents users from accidentally overwriting a DE.
|
75
|
+
Previous access through cCxxx[] or dxxxx[] will fail now.
|
76
|
+
* Some parameters of EDI::E::Interchange.new have changed,
|
77
|
+
several more added.
|
78
|
+
* UNA handling: Now through attributes of UNA class.
|
79
|
+
|
80
|
+
=== Misc improvements
|
81
|
+
* Improved handling of both decimal signs
|
82
|
+
* Bug removed in escaping of special chars
|
83
|
+
* Refactoring of some internals
|
84
|
+
* RDoc now usable - completely overhauled.
|
85
|
+
|
86
|
+
== 0.9.2
|
87
|
+
=== Misc improvements
|
88
|
+
* New parsing basics: "String::separate" replaced by EDI::E::edi_split
|
89
|
+
* Parsing bug removed
|
90
|
+
* More validation features
|
91
|
+
* "inspect", "find_all" added
|
92
|
+
* "descendants_and_self" etc. added to EDI::Segment
|
93
|
+
* bin/editool.rb added (validate, list, inspect EDI data)
|
94
|
+
|
95
|
+
== 0.9.1
|
96
|
+
=== First release as a gem
|
97
|
+
* Split from the edi4r core package, turned into a separate one.
|
98
|
+
* Unit tests added
|
99
|
+
* RDoc documentation added
|
100
|
+
* Modular loading scheme for normdata added (through EDI_NDB_PATH)
|
101
|
+
* Parameter usage overhauled
|
102
|
+
* Rolled into a gem
|
103
|
+
|
104
|
+
== 0.8.x
|
105
|
+
* Used internally for projects and teaching
|
106
|
+
|
data/README
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= EDI TOOLKIT for RUBY (edi4r)
|
2
|
+
|
3
|
+
This is Ruby gem <b>edi4r</b> version
|
4
|
+
:include:VERSION
|
5
|
+
|
6
|
+
Edi4r was created to greatly simplify the creation and processing of data for
|
7
|
+
Electronic Data Interchange (EDI). In particular, it supports the UN/EDIFACT
|
8
|
+
syntax (ISO 9573) and optionally SAP IDocs.
|
9
|
+
|
10
|
+
== Installation
|
11
|
+
|
12
|
+
Install it as any other Ruby gem, e.g.:
|
13
|
+
|
14
|
+
sudo gem install edi4r-<version>.gem
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require_gem 'edi4r'
|
20
|
+
require 'edi4r/edifact' # optional
|
21
|
+
|
22
|
+
# Build a UN/EDIFACT interchange from its character representation in a file:
|
23
|
+
|
24
|
+
ic = nil
|
25
|
+
File.open("received.edi") {|hnd| ic = EDI::E::Interchange.parse( hnd ) }
|
26
|
+
ic.each do |msg|
|
27
|
+
# Process message, here: Just list document numbers from (only) segment BGM
|
28
|
+
puts msg['BGM'].first.d1004
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a minimalistic interchange
|
32
|
+
|
33
|
+
ic = EDI::E::Interchange.new # Default: syntax version=3, charset = UNOB
|
34
|
+
msg = ic.new_message # Default: ORDERS D.96A
|
35
|
+
|
36
|
+
bgm = msg.new_segment('BGM') # Obtain an empty segment
|
37
|
+
bgm.cC002.d1001 = '220' # Add some content to mandatory elements
|
38
|
+
bgm.d1004 = 'PO-54321'
|
39
|
+
dtm = msg.new_segment('DTM')
|
40
|
+
dtm.cC507.d2005 = '137'
|
41
|
+
uns = msg.new_segment('UNS')
|
42
|
+
uns.d0081 = 'S'
|
43
|
+
[bgm, dtm, uns].each {|seg| msg.add seg} # Add segments to message
|
44
|
+
ic.add msg # Add message to interchange - ready to go!
|
45
|
+
|
46
|
+
ic.header.cS002.d0004 = 'sender'; ic.header.cS003.d0010 = 'recipient' # UNB
|
47
|
+
ic.validate # Conforming to standard?
|
48
|
+
print ic # Could be sent that way!
|
49
|
+
|
50
|
+
== See also
|
51
|
+
|
52
|
+
* Background[link:files/lib/edi4r_rb.html] info about data structure and classes.
|
53
|
+
|
54
|
+
* A Tutorial[link:files/Tutorial.html] for examples of use.
|
55
|
+
|
56
|
+
* A ChangeLog[link:files/ChangeLog.html] will be maintained; it is just starting now.
|
57
|
+
|
58
|
+
* Finally, see TO-DO[link:files/TO-DO.html] for the current wish list.
|
59
|
+
|
60
|
+
* This code is put under the Ruby license, see COPYING[link:files/COPYING.html] for details.
|
61
|
+
|
62
|
+
:include:AuthorCopyright
|
63
|
+
|
64
|
+
Enjoy,
|
65
|
+
|
66
|
+
Heinz
|
data/TO-DO
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
= To-do list
|
2
|
+
|
3
|
+
== Documentation
|
4
|
+
* Further improve RDoc support
|
5
|
+
* DocBook based tutorial
|
6
|
+
* FAQ section
|
7
|
+
* Intro to EDI in general
|
8
|
+
* Outlook: More components (of a real EDI/EAI server), e.g. for messaging
|
9
|
+
|
10
|
+
== XML support
|
11
|
+
* DTD generation from Diagrams
|
12
|
+
|
13
|
+
== Core features
|
14
|
+
* E: more SV4 support, I-EDI support
|
15
|
+
* E: Support for code lists, subsets, restricted codelists
|
16
|
+
* Recovery from parsing errors, ability to skip faulty messages
|
17
|
+
* Consider a "C" extension for fast segmentation of plain EDIFACT data
|
18
|
+
* Generic support for CSV/variable-length and fixed record formats?
|
19
|
+
* Add a generic number generator
|
20
|
+
* Add EAN/GTIN/EPC helpers
|
21
|
+
|
22
|
+
== XPath support
|
23
|
+
* method "each" accepting XPath parameter
|
24
|
+
* Index op [] accepting XPath
|
25
|
+
* More XPath expressions + corresponding methods
|
26
|
+
|
27
|
+
== Applications
|
28
|
+
* Better list mode in editool.rb
|
29
|
+
|
30
|
+
|
31
|
+
== Test suite
|
32
|
+
* Extend
|
33
|
+
|
34
|
+
== Details
|
35
|
+
* Avoid module name 'Dir' - might collide with std. classname 'Dir'
|
data/Tutorial
ADDED
@@ -0,0 +1,609 @@
|
|
1
|
+
= Tutorial
|
2
|
+
|
3
|
+
== Getting started
|
4
|
+
|
5
|
+
=== Installation
|
6
|
+
|
7
|
+
sudo gem install edi4r
|
8
|
+
sudo gem install edi4r-tdid
|
9
|
+
|
10
|
+
=== Require statements
|
11
|
+
|
12
|
+
require "rubygems"
|
13
|
+
require "edi4r" # Try require_gem if this fails on your site
|
14
|
+
require "edi4r/edifact"
|
15
|
+
require "edi4r-tdid" # Try require_gem if this fails on your site
|
16
|
+
|
17
|
+
|
18
|
+
== Creating an (outbound) UN/EDIFACT interchange
|
19
|
+
|
20
|
+
=== An empty interchange
|
21
|
+
|
22
|
+
ic = EDI::E::Interchange.new
|
23
|
+
|
24
|
+
creates an empty interchange object with syntax version 3
|
25
|
+
and charset UNOB. You can make this a bit more explicit
|
26
|
+
by passing parameters as hash components:
|
27
|
+
|
28
|
+
ic = EDI::E::Interchange.new( :version => 3, :charset => 'UNOB' )
|
29
|
+
|
30
|
+
See the source for more parameters.
|
31
|
+
|
32
|
+
=== An empty message
|
33
|
+
|
34
|
+
msg = ic.new_message
|
35
|
+
|
36
|
+
creates an empty message in the context of the given interchange,
|
37
|
+
i.e. the syntax version, charset, UNA settings, interactive or batch EDI.
|
38
|
+
|
39
|
+
By default, the message type is <tt>ORDERS D.96A</tt>. Select any
|
40
|
+
message from any UN/TDID by passing the corresponding parameters
|
41
|
+
as hash components:
|
42
|
+
|
43
|
+
msg = ic.new_message( :msg_type=>'ORDERS', :version=>'D', :release=>'96A',
|
44
|
+
:resp_agency=>'UN' )
|
45
|
+
|
46
|
+
Hash components which you do not specify are taken from a set of defaults.
|
47
|
+
|
48
|
+
|
49
|
+
=== Filling an interchange
|
50
|
+
|
51
|
+
You may add messages to the interchange any time by calling method add():
|
52
|
+
|
53
|
+
ic.add( msg )
|
54
|
+
|
55
|
+
When adding new messages to an interchange, they get appended to the
|
56
|
+
current interchange content. There is no method to insert a message
|
57
|
+
at any other location. If you need to do that, hold your messages
|
58
|
+
in an array, sort them any way you like, and finally add them
|
59
|
+
to the interchange in the desired sequence.
|
60
|
+
|
61
|
+
Note that each messag gets validated by default when you add it to
|
62
|
+
the interchange. If your message needs to be completed only later,
|
63
|
+
you may disable validation by calling:
|
64
|
+
|
65
|
+
ic.add( msg, false )
|
66
|
+
|
67
|
+
=== Filling a message
|
68
|
+
|
69
|
+
A freshly created message is empty, aside from its header and trailer
|
70
|
+
which we shall discuss later. Simply create the segments you want to add,
|
71
|
+
fill them, and add them to the message:
|
72
|
+
|
73
|
+
seg = msg.new_segment( 'BGM' )
|
74
|
+
|
75
|
+
Here, we derived a BGM segment from the current context,
|
76
|
+
i.e. an UN/TDID like D.96A which we specified when creating the message given.
|
77
|
+
|
78
|
+
Note that <tt>new_segment()</tt> accepts all segment tags available in the
|
79
|
+
whole TDID's segment directory - not just those usable within
|
80
|
+
this message type.
|
81
|
+
|
82
|
+
Add content to the segment (see below) and add it to the message:
|
83
|
+
|
84
|
+
msg.add( seg )
|
85
|
+
|
86
|
+
Like with messages added to an interchange, it is your responsibility
|
87
|
+
to assure the proper sequence of segments. You will need the UN/EDIFACT
|
88
|
+
message structure, a subset description, or a message implementation
|
89
|
+
guideline (MIG) handy in order to comply.
|
90
|
+
|
91
|
+
It is possible to add empty or partially filled segments to a message.
|
92
|
+
Just keep a reference to them and fill in their required data elements later.
|
93
|
+
|
94
|
+
|
95
|
+
== Accessing Composites and Data Elements
|
96
|
+
|
97
|
+
=== Background
|
98
|
+
|
99
|
+
While interchanges and messages are basically empty when created,
|
100
|
+
segments are not: They come equipped with the composites (CDE, composite
|
101
|
+
data elements ) and data elements (DE) they comprise in their current
|
102
|
+
context. Likewise, CDEs come equipped with the sequence of DEs
|
103
|
+
which they contain according to the underlying TDID.
|
104
|
+
|
105
|
+
Segments and CDEs are basically sequences (arrays) of their component
|
106
|
+
elements. This sequence depends on the TDID of their context.
|
107
|
+
E.g., a BGM segment from D.96A looks different from a BGM in D.01B.
|
108
|
+
These sequences are fixed and cannot/should not be altered after
|
109
|
+
creation of a segment or CDE.
|
110
|
+
|
111
|
+
=== Getters for CDE
|
112
|
+
|
113
|
+
There is a getter method for each CDE of a segment.
|
114
|
+
Its name is simply prefix 'c' and the CDE name.
|
115
|
+
|
116
|
+
In order to access, say, C002 in segment BGM, the getter
|
117
|
+
is named 'c' + 'C002':
|
118
|
+
|
119
|
+
cde = seg.cC002
|
120
|
+
|
121
|
+
(See below how to handle arrays)
|
122
|
+
|
123
|
+
In most cases you'll need the CDE object only to access
|
124
|
+
its component data elements. Let's see how that works:
|
125
|
+
|
126
|
+
=== Getters for DE values
|
127
|
+
|
128
|
+
During mapping tasks, we normally do not deal with the
|
129
|
+
internal organization of DE objects. All we need is access
|
130
|
+
to their <em>values</em>.
|
131
|
+
|
132
|
+
Similarly to CDE getters, we build a DE getter by prepending
|
133
|
+
a 'd' to the DE name. The result is a method that returns
|
134
|
+
the current value of this DE (nil if unassigned), not the
|
135
|
+
DE object itself:
|
136
|
+
|
137
|
+
order_number = seg.d1004 if cde.d1001 == 220
|
138
|
+
|
139
|
+
The example shows that this concept is very convenient and works
|
140
|
+
both with component DEs in a CDE and plain DEs in a segment.
|
141
|
+
|
142
|
+
=== Setters for DE values
|
143
|
+
|
144
|
+
We use the same approach for setters - a DE setter actually
|
145
|
+
changes its value, not the DE object itself:
|
146
|
+
|
147
|
+
bgm = msg.new_segment( 'BGM' )
|
148
|
+
bgm.d1004 = '123456ABC'
|
149
|
+
bgm.cC002.d1001 = 220
|
150
|
+
|
151
|
+
Well, that's both easy and readable! But what about the
|
152
|
+
integer assigned to DE 1004? Don't worry - its string value
|
153
|
+
is still what we later want to see in the interchange file.
|
154
|
+
|
155
|
+
=== Setters for CDE and segments?
|
156
|
+
|
157
|
+
There is no such thing! A CDE should always be derived
|
158
|
+
from its proper context and must not be changed thereafter.
|
159
|
+
Likewise, a segment's content is nothing a user should
|
160
|
+
interfere with.
|
161
|
+
|
162
|
+
Does that sound like dictatorship? Well, there *are* ways
|
163
|
+
to manipulate CDEs and segments, but that's an advanced
|
164
|
+
and rarely used topic well out of scope of this tutorial ...
|
165
|
+
|
166
|
+
=== DE and CDE arrays
|
167
|
+
|
168
|
+
Sometimes, a DE occurs multiple times within a segment or CDE,
|
169
|
+
and a CDE may occur multiple times within a segment.
|
170
|
+
|
171
|
+
Before syntax version 4, EDIFACT does not really employ
|
172
|
+
the concept of an array. Instead, there are multiple
|
173
|
+
occurrences of a particular DE or CDE listed in a row.
|
174
|
+
|
175
|
+
In such a case, the corresponding getters and setters
|
176
|
+
won't work. Actually, they raise a TypeError to make sure
|
177
|
+
that you don't accidentally overlook that there is more
|
178
|
+
than one instance of the given (C)DE.
|
179
|
+
|
180
|
+
We obtain a whole array of *all* matching (C)DE instead
|
181
|
+
and indicate this with a prefix 'a':
|
182
|
+
|
183
|
+
seg = msg.new_segment('PIA')
|
184
|
+
cde_list = seg.aC212
|
185
|
+
|
186
|
+
cde_list[0].d7140 = '54321'
|
187
|
+
cde_list[0].d7143 = 'SA'
|
188
|
+
cde_list[0].d3055 = 91
|
189
|
+
|
190
|
+
cde_list[1].d7140 = ... # etc
|
191
|
+
|
192
|
+
seg = msg.new_segment('NAD')
|
193
|
+
seg.cC080.a3036[0].value = 'E. X. Ample'
|
194
|
+
seg.cC080.a3036[1].value = 'Sales dept.'
|
195
|
+
|
196
|
+
Note that a3036 returns an array of DE objects, not their values!
|
197
|
+
We thus use DE setter <tt>value</tt> to actually assign
|
198
|
+
new values to those DE objects.
|
199
|
+
|
200
|
+
Sometimes, a CDE or segment contains the same DE more than once
|
201
|
+
even if both instances are separated by a different element,
|
202
|
+
like DE 3055 and 1131 in C088 of segment FII, which you may
|
203
|
+
find in invoices. In that case the same concept holds: cde.a1131
|
204
|
+
would fetch all instances, no matter if some other elements
|
205
|
+
occur in between or not.
|
206
|
+
|
207
|
+
|
208
|
+
== Building it all together
|
209
|
+
|
210
|
+
OK, so we keep generating segments, filling their data elements
|
211
|
+
with content, and adding them to the message that we are
|
212
|
+
about to build - fine.
|
213
|
+
|
214
|
+
Likewise, we add messages to the interchange. Fine - but how do we
|
215
|
+
eventually get the output, how do we make sure that we have
|
216
|
+
not forgotten anything, and how do we deal with the
|
217
|
+
service segments (e.g. to define sender and recipient IDs) ?
|
218
|
+
|
219
|
+
=== Headers and trailers
|
220
|
+
|
221
|
+
Interchanges and messages are objects which may come with
|
222
|
+
a header and trailer segment. In UN/EDIFACT, these are
|
223
|
+
UNB/UNZ and UNH/UNT, respectively.
|
224
|
+
|
225
|
+
In order to let us focus on the content, edi4r keeps these
|
226
|
+
service segments away from us and tries its best to
|
227
|
+
treat them automatically. For example, we do not have to
|
228
|
+
count segments or messages - edi4r takes care of that
|
229
|
+
and updates the corresponding DE in UNT and UNZ, respectively.
|
230
|
+
|
231
|
+
If we really need to access data there,
|
232
|
+
that's possible anytime through getters <tt>header</tt> and
|
233
|
+
<tt>trailer</tt>. From then on, we just use the usual DE and
|
234
|
+
CDE getters and setters. E.g., setting the UNB sender ID
|
235
|
+
works like this:
|
236
|
+
|
237
|
+
ic.header.cS002.d0004 = '1234567'
|
238
|
+
|
239
|
+
Setting the test indicator may look like this:
|
240
|
+
|
241
|
+
ic.header.d0035 = 1
|
242
|
+
|
243
|
+
=== UNA handling
|
244
|
+
|
245
|
+
The pseudo segment UNA commonly introduces an UN/EDIFACT
|
246
|
+
interchange. It is shown there by default, which is a good
|
247
|
+
idea in most cases. If you really have to switch it off, use:
|
248
|
+
|
249
|
+
ic.show_una = false
|
250
|
+
|
251
|
+
It can be easily viewed e.g. by:
|
252
|
+
|
253
|
+
puts ic.una
|
254
|
+
|
255
|
+
The six special characters it containes are both readable
|
256
|
+
and modifiable. Please note that these characters are represented
|
257
|
+
as their ASCII integer codes, not as one-character strings.
|
258
|
+
Here is the list of corresponding getter methods:
|
259
|
+
|
260
|
+
ce_sep() # Component data element separator, default ?:
|
261
|
+
de_sep() # Data element separator, default: ?+
|
262
|
+
decimal_sign() # Both ?. and ?, are eligible
|
263
|
+
esc_char() # Escape character, default: ??
|
264
|
+
rep_sep() # Repetition occurrence indicator, default:
|
265
|
+
# ? (SV1-3), ?* (syntax version 4)
|
266
|
+
seg_term() # Segment terminator, default: ?'
|
267
|
+
|
268
|
+
Corresponding setters allow you to change all of them.
|
269
|
+
Remember to pass ASCII values, not strings. Example:
|
270
|
+
|
271
|
+
pri.cC509.d5118 = 30.1
|
272
|
+
pri.to_s --> "PRI+AAA:30.1::LIU"
|
273
|
+
ic.una.decimal_sign = ?,
|
274
|
+
pri.to_s --> "PRI+AAA:30,1::LIU"
|
275
|
+
ic.una.ce_sep = ?/
|
276
|
+
pri.to_s --> "PRI+AAA/30,1//LIU"
|
277
|
+
|
278
|
+
=== Validation
|
279
|
+
|
280
|
+
Edi4r comes with a set of built-in validation rules. In order to
|
281
|
+
validate your interchange, just call
|
282
|
+
|
283
|
+
ic.validate
|
284
|
+
|
285
|
+
This method will return the number of (recoverable) issues found.
|
286
|
+
Error messages and warnings are written to $stderr. There are
|
287
|
+
a few non-recoverable errors which raise exceptions.
|
288
|
+
|
289
|
+
Actually, you may apply method validate() to almost all of the
|
290
|
+
EDI objects mentioned so far. However, doing this once for the
|
291
|
+
whole interchance will validate everything.
|
292
|
+
|
293
|
+
=== Printing and saving
|
294
|
+
|
295
|
+
Once our data is validated, we want to send it to our business partner.
|
296
|
+
Actually, that's very simple: Output it e.g. to stdout or to a file
|
297
|
+
by just printing it! This works nicely, because our EDI objects are
|
298
|
+
all equipped with reasonable methods "to_s()".
|
299
|
+
|
300
|
+
|
301
|
+
== Processing (inbound) interchanges
|
302
|
+
|
303
|
+
=== Building the interchange object
|
304
|
+
|
305
|
+
We build a whole interchange with a single call by passing
|
306
|
+
its character representation as a String or IO object to class method
|
307
|
+
parse():
|
308
|
+
|
309
|
+
ic = nil
|
310
|
+
File.open("inbound_01.edi") {|hnd| ic = EDI::E::Interchange.parse( hnd )}
|
311
|
+
|
312
|
+
That's it - from now on we may access all its contents in much the
|
313
|
+
same way as we did during generation.
|
314
|
+
|
315
|
+
=== Iterating over messages
|
316
|
+
|
317
|
+
Typically we'd like to loop through the messages contained:
|
318
|
+
|
319
|
+
ic.each { |msg| map_message( msg ) }
|
320
|
+
|
321
|
+
with some suitably created mapping procedure map_message().
|
322
|
+
In this context, the interchange is treated as a container
|
323
|
+
of messages. We therefore use the standard iterator each().
|
324
|
+
|
325
|
+
Actually, index access is also available, as are following
|
326
|
+
array-like methods:
|
327
|
+
[](int), index(obj), each(&b), find_all(&b), size(),
|
328
|
+
length(), first(), last().
|
329
|
+
|
330
|
+
Examples:
|
331
|
+
second_msg = ic[1]
|
332
|
+
last_msg = ic.last
|
333
|
+
|
334
|
+
|
335
|
+
=== Awaiting segments of a message
|
336
|
+
|
337
|
+
Similarly, we iterate through the segments of a message. The following
|
338
|
+
construction lets you select segments in their proper context
|
339
|
+
(which is the segment group):
|
340
|
+
|
341
|
+
def map_message( msg )
|
342
|
+
# do your initialization here, then
|
343
|
+
|
344
|
+
msg.each do |seg|
|
345
|
+
seg_name = seg.name
|
346
|
+
seg_name += ' ' + seg.sg_name if seg.sg_name
|
347
|
+
case seg_name
|
348
|
+
when "BGM"
|
349
|
+
# do this ...
|
350
|
+
when "DTM"
|
351
|
+
# do that ...
|
352
|
+
when 'NAD SG2'
|
353
|
+
# react only if NAD occurs in segment group 2
|
354
|
+
|
355
|
+
# ... etc., finally:
|
356
|
+
default
|
357
|
+
raise "Segment #{seg_name}: Not accounted for!"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
If you need to obtain all segments with a given tag, pass the
|
362
|
+
tag as a string to the index operator:
|
363
|
+
|
364
|
+
d = msg['DTM'] # Array of all DTM segments, any segment group
|
365
|
+
|
366
|
+
Actually, a message behaves like a container just as an interchange does,
|
367
|
+
so the array-like methods listed in the previous section also apply here:
|
368
|
+
|
369
|
+
d = msg.find_all {|seg| seg.name == 'DTM' && seg.sg_name == 'SG20'}
|
370
|
+
|
371
|
+
=== Selecting segment group instances
|
372
|
+
|
373
|
+
Iterating over all segments of a message sequentially tends to produce
|
374
|
+
cluttered code when messages grow complex. Wouldn't it be nice
|
375
|
+
if we could delegate e.g. the mapping of a whole NAD group to a
|
376
|
+
specialized procedure? Still better - could we delegate mapping
|
377
|
+
of a whole item group to a specialized routine?
|
378
|
+
|
379
|
+
Actually that's quite easy when we recall that segment groups
|
380
|
+
are side branches of the main trunk (or a higher-level branch)
|
381
|
+
of a message diagram, with their trigger segments being the
|
382
|
+
T-shaped "joints". Thus, segments of a segment group are mere
|
383
|
+
descendants of their trigger segment in the branching diagram.
|
384
|
+
|
385
|
+
Here are some helpful methods to select segments of a group:
|
386
|
+
# ...
|
387
|
+
when 'NAD SG2'
|
388
|
+
map_nad_sg2( seg.children_and_self ) # skip segment COM...
|
389
|
+
# ...
|
390
|
+
when 'LIN SG28'
|
391
|
+
map_item( seg.descendants_and_self )
|
392
|
+
# ...
|
393
|
+
|
394
|
+
Methods
|
395
|
+
descendants(), descendants_and_self(), children(), and
|
396
|
+
children_and_self()
|
397
|
+
are inspired by XPath axes. They return an array of segments
|
398
|
+
which depend on the given (trigger) segment as their common
|
399
|
+
ancestor.
|
400
|
+
|
401
|
+
Using these selectors, writing modular mapping code
|
402
|
+
now becomes an easy task.
|
403
|
+
|
404
|
+
|
405
|
+
== Peeking into interchanges
|
406
|
+
|
407
|
+
=== Background
|
408
|
+
|
409
|
+
Sometimes we only need to extract some header information
|
410
|
+
out of EDI files, e.g. in order to find out whether the content
|
411
|
+
is UN/EDIFACT, who sent it to whom, or just to see if the
|
412
|
+
UNB test indicator is set.
|
413
|
+
|
414
|
+
We could of course apply EDI::E::Interchange.parse() and access
|
415
|
+
the header of the resulting interchange object when we know that
|
416
|
+
a given file contains UN/EDIFACT data. However, that would be
|
417
|
+
a big waste of resources, especially for large interchanges.
|
418
|
+
|
419
|
+
=== Method "peek()" for UN/EDIFACT data
|
420
|
+
|
421
|
+
Edi4r instead offers method "peek()". It reads just enought bytes
|
422
|
+
from the file to determine its contents and to decode the header.
|
423
|
+
Like "parse()" it returns an Interchange object, but that one
|
424
|
+
is empty except for the header (and a dummy trailer) segment.
|
425
|
+
|
426
|
+
You can then extract any header element you need through the
|
427
|
+
usual getters. Example: Find out if the test indicator is set.
|
428
|
+
|
429
|
+
def is_testdata?( hnd )
|
430
|
+
ic = EDI::E::Interchange.peek( hnd )
|
431
|
+
ic.d0035 == '1' || ic.d0035 == 1
|
432
|
+
end
|
433
|
+
|
434
|
+
=== Auto-detection and implicit decompression
|
435
|
+
|
436
|
+
Regular EDI users need to archive their business data.
|
437
|
+
In simple cases, moving interchange files into proper folders
|
438
|
+
after successfully processing them already does the job.
|
439
|
+
|
440
|
+
You can save a lot of space though by compressing them.
|
441
|
+
Applying "gzip" to EDIFACT data easily shrinks them to
|
442
|
+
10 % of their original volume.
|
443
|
+
|
444
|
+
So far, so good. Later though, you may need to extract
|
445
|
+
a specific file from the archive, e.g. the interchange
|
446
|
+
with control reference "ref3456" from a customer
|
447
|
+
with sender id "xyz". Well, you do not need to maintain
|
448
|
+
a separate index or decompress all files in the archive
|
449
|
+
in order to find it. There is a generic class method
|
450
|
+
"Interchange.peek()" that does all this for you.
|
451
|
+
Consider the following code fragment that assumes
|
452
|
+
that ARGV contains the list of files to search:
|
453
|
+
|
454
|
+
require 'zlib'
|
455
|
+
|
456
|
+
found = ARGV.find do |fname|
|
457
|
+
ic = EDI::Interchange.peek(File.open(fname))
|
458
|
+
h = ic.header
|
459
|
+
ic.syntax=='E' && h.cS002.d0004=='xyz' && h.d0020=='ref1234'
|
460
|
+
end
|
461
|
+
ic = EDI::Interchange.parse(File.open(found))
|
462
|
+
# ...
|
463
|
+
|
464
|
+
Note that this code will work with both zipped and
|
465
|
+
unzipped data, and with UN/EDIFACT as well as other content.
|
466
|
+
|
467
|
+
|
468
|
+
== XML representation
|
469
|
+
|
470
|
+
=== Background
|
471
|
+
|
472
|
+
EDI interchanges may be regarded as abstract objects which need
|
473
|
+
some representation when stored or exchanged. E.g., UN/EDIFACT
|
474
|
+
interchanges may be expressed (represented) by syntax version 1-4
|
475
|
+
and a choice of separator and termination characters
|
476
|
+
without changing their identity. Likewise, interchanges
|
477
|
+
may be represented by some suitable XML document type.
|
478
|
+
|
479
|
+
There was a time when classical EDI representations were
|
480
|
+
considered outdated and to be replaced by XML documents.
|
481
|
+
We know by now that a mere change in representation does not help
|
482
|
+
to resolve the real issues of e-business. Nonetheless,
|
483
|
+
XML-based technology has become much more wide-spread than EDI,
|
484
|
+
so chances are high that one has to integrate classical EDI data
|
485
|
+
into a XML-driven architecture.
|
486
|
+
|
487
|
+
There are (too) many ways to do this, and attempts have been made
|
488
|
+
to standardize them. In particular, DIN 16557-4
|
489
|
+
(http://www.beuth.de/cmd?workflowname=CSVList&websource=&artid=43768898)
|
490
|
+
describes a way how to represent UN/EDIFACT interchanges as XML documents
|
491
|
+
and how to describe them with DTDs.
|
492
|
+
|
493
|
+
The DIN approach however focuses only on UN/EDIFACT and does not represent
|
494
|
+
the logical structure of documents. It merely encodes segments as
|
495
|
+
XML elements and data elements and composites as their attributes.
|
496
|
+
The value of DTDs is limited, as they need to be generated for
|
497
|
+
any particular interchange and lack information about mandatory
|
498
|
+
data elements and CDEs, let alone code lists.
|
499
|
+
|
500
|
+
This library therefore supports a generic approach that allows us
|
501
|
+
to represent any interchange object as an XML document, be it
|
502
|
+
a UN/EDIFACT interchange, a file of SAP IDocs, or an ANSI
|
503
|
+
X.12 or other interchange when such a module becomes available.
|
504
|
+
Native and XML representation are fully interchangeable,
|
505
|
+
and the XML representation reflects information from the
|
506
|
+
branching diagram, thus supporting considerably XPath-based
|
507
|
+
processing (e.g. you could easily select an instance of
|
508
|
+
a line item group). Formal validation through a single generic DTD
|
509
|
+
is available, while in-depth validation remains available
|
510
|
+
through this library at the abstract level.
|
511
|
+
|
512
|
+
=== Generating an XML representation of an interchange
|
513
|
+
|
514
|
+
The current implementation of XML features is built on
|
515
|
+
Ruby's REXML module (alternative implementations are conceivable).
|
516
|
+
Simply load the additional methods <em>after</em> loading all other
|
517
|
+
optional EDI4R modules, then use method "to_xml()" like this:
|
518
|
+
|
519
|
+
# Other require statements, finally:
|
520
|
+
require "edi4r/rexml"
|
521
|
+
|
522
|
+
# Generate your interchange "ic", then:
|
523
|
+
xdoc = REXML::Document.new # Empty REXML document
|
524
|
+
ic.to_xml( xdoc ) # Fill it
|
525
|
+
|
526
|
+
# The rest is standard REXML handling. Here, we write the xdoc to a file.
|
527
|
+
# (See REXML::Document.write() for details on indenting)
|
528
|
+
xdoc.write( File.open( 'mydata.xml','w'), 0 )
|
529
|
+
|
530
|
+
=== Building an interchange from its XML representation
|
531
|
+
|
532
|
+
No matter what EDI standard the interchange represents,
|
533
|
+
its corresponding EDI4R object can be re-generated easily.
|
534
|
+
Just make sure that you have loaded the corresponding module(s):
|
535
|
+
|
536
|
+
# Other require statements, finally:
|
537
|
+
require "edi4r/rexml"
|
538
|
+
|
539
|
+
ic = EDI::Interchange.parse( File.open('mydata.xml') )
|
540
|
+
|
541
|
+
Yes, that's right: It's the same statement that would
|
542
|
+
also load UN/EDIFACT data!
|
543
|
+
|
544
|
+
If you know already what to expect, you might bypass EDI4R's
|
545
|
+
auto-detection and directly call one of the parse_xml() methods:
|
546
|
+
|
547
|
+
xdoc = REXML::Document.new( File.open('mydata.xml') )
|
548
|
+
ic = EDI::E::Interchange.parse_xml( xdoc )
|
549
|
+
|
550
|
+
=== Utilities: edi2xml.rb, xml2edi.rb
|
551
|
+
|
552
|
+
These two scripts are included to further simplify your transition
|
553
|
+
between traditional EDI representation and XML representation.
|
554
|
+
They are command-line tools that simply wrap the library calls
|
555
|
+
mentioned above. Example:
|
556
|
+
|
557
|
+
$ edi2xml.rb foo.edi > foo.xml
|
558
|
+
$ xml2edi.rb foo.xml > bar.edi
|
559
|
+
$ diff foo.edi bar.edi # There should be no differences
|
560
|
+
|
561
|
+
== Tools
|
562
|
+
|
563
|
+
=== editool.rb
|
564
|
+
|
565
|
+
Use this command-line tool e.g. when you want to
|
566
|
+
* <b>list</b> UN/EDIFACT data in a readable way (one segment per line,
|
567
|
+
optionally indented according to segment level),
|
568
|
+
* <b>validate</b> your EDI data
|
569
|
+
* <b>analyze</b> the data more thoroughly through method "inspect()"
|
570
|
+
* <b>report</b> header data quickly, one line per file
|
571
|
+
|
572
|
+
Called without option, it just builds an internal memory model
|
573
|
+
of the passed file(s) and raises an exception upon parsing errors.
|
574
|
+
Thorough validation can be requested optionally. Example:
|
575
|
+
|
576
|
+
$ editool.rb -l foo.edi bar.edi
|
577
|
+
$ editool.rb -p *.edi *.xml
|
578
|
+
|
579
|
+
|
580
|
+
== Further reading
|
581
|
+
|
582
|
+
A pair of full-blown mappings (inhouse-to-EANCOM, EANCOM-to-inhouse)
|
583
|
+
shows in much more detail how to do outbound and inbound mapping.
|
584
|
+
See the source codes in the "test" folder!
|
585
|
+
|
586
|
+
|
587
|
+
== Misc topics
|
588
|
+
|
589
|
+
To be supplied later; currently just a room to collect keywords to cover.
|
590
|
+
|
591
|
+
=== Debugging and viewing
|
592
|
+
|
593
|
+
:linebreak, :indented
|
594
|
+
inspect()
|
595
|
+
Exception classes
|
596
|
+
Segments: T-nodes, ordinal number, index, occurrence, max. occurrence
|
597
|
+
empty?, required?
|
598
|
+
|
599
|
+
=== More advances features
|
600
|
+
|
601
|
+
Validation: warnings and exceptions (logging?)
|
602
|
+
Add-ons to class Time
|
603
|
+
Consistency of references common to headers and trailers
|
604
|
+
Inheriting settings from parent: UNH from UNG, UNG from UNB
|
605
|
+
Low-level access to Collection contents
|
606
|
+
|
607
|
+
Enjoy,
|
608
|
+
|
609
|
+
-- Heinz
|