edi4r 0.9.4.1
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.
- 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
|