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/lib/edi4r/rexml.rb
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# Add-on to EDI module "EDI4R"
|
2
|
+
# Classes for XML support, here: Basic classes
|
3
|
+
#
|
4
|
+
# :include: ../../AuthorCopyright
|
5
|
+
#
|
6
|
+
# $Id: rexml.rb,v 1.1 2006/08/01 11:14:29 werntges Exp $
|
7
|
+
#--
|
8
|
+
# $Log: rexml.rb,v $
|
9
|
+
# Revision 1.1 2006/08/01 11:14:29 werntges
|
10
|
+
# Initial revision
|
11
|
+
#
|
12
|
+
#++
|
13
|
+
# To-do list:
|
14
|
+
# all - Just starting this...
|
15
|
+
#
|
16
|
+
# This is the REXML module of edi4r
|
17
|
+
#
|
18
|
+
# It adds methods to most of the basic classes which enable EDI objects
|
19
|
+
# to represent themselves in a generic XML document type, and to read back
|
20
|
+
# instances of this document type.
|
21
|
+
#
|
22
|
+
# This version of XML support for EDI4R relies on REXML.
|
23
|
+
|
24
|
+
require 'rexml/document'
|
25
|
+
# require 'diagrams-xml'
|
26
|
+
require 'edi4r/edifact-rexml' if EDI.constants.include? 'E'
|
27
|
+
require 'edi4r/idoc-rexml' if EDI.constants.include? 'I'
|
28
|
+
|
29
|
+
module EDI
|
30
|
+
|
31
|
+
#########################################################################
|
32
|
+
#
|
33
|
+
# Utility: Separator method for UN/EDIFACT segments/CDEs
|
34
|
+
#
|
35
|
+
|
36
|
+
class Collection_S
|
37
|
+
|
38
|
+
def to_xml( xel_parent, instance=1 )
|
39
|
+
xel = REXML::Element.new( normalized_class_name )
|
40
|
+
xel.attributes["name"] = @name
|
41
|
+
xel.attributes["instance"] = instance if instance > 1
|
42
|
+
xel_parent.elements << xel
|
43
|
+
instance_counter = Hash.new(0)
|
44
|
+
each do |obj|
|
45
|
+
i = (instance_counter[obj.name] += 1)
|
46
|
+
obj.to_xml( xel, i ) unless obj.empty?
|
47
|
+
end
|
48
|
+
xel
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
class Collection_HT
|
54
|
+
|
55
|
+
def to_xml( xel_parent )
|
56
|
+
xel = REXML::Element.new( normalized_class_name )
|
57
|
+
xel.attributes["name"] = @name
|
58
|
+
xel_parent.elements << xel
|
59
|
+
|
60
|
+
xhd = to_xml_header( xel )
|
61
|
+
each { |obj| obj.to_xml( xel ) }
|
62
|
+
xtr = to_xml_trailer( xel )
|
63
|
+
[xel, xhd, xtr] # You might want to add something ...
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def to_xml_header( xparent )
|
68
|
+
if @header
|
69
|
+
xparent << (xel = REXML::Element.new( 'Header' ))
|
70
|
+
@header.to_xml( xel )
|
71
|
+
return xel
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_xml_trailer( xparent )
|
77
|
+
if @trailer
|
78
|
+
xparent << (xel = REXML::Element.new( 'Trailer' ))
|
79
|
+
@trailer.to_xml( xel )
|
80
|
+
return xel
|
81
|
+
end
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
class Interchange
|
89
|
+
|
90
|
+
# This is a dispatcher method for your convenience, similar to
|
91
|
+
# EDI::Interchange.parse. It may be used for all supported EDI standards.
|
92
|
+
#
|
93
|
+
# hnd:: A REXML document or something that can be turned into one,
|
94
|
+
# i.e. an IO object or a String object with corresponding contents
|
95
|
+
#
|
96
|
+
# Returns an Interchange object of the subclass specified by the
|
97
|
+
# "standard_key" atribute of the root element, e.g. a EDI::E::Interchange.
|
98
|
+
#
|
99
|
+
def Interchange.parse_xml( hnd ) # Handle to REXML document
|
100
|
+
unless hnd.is_a? REXML::Document or hnd.is_a? IO or hnd.is_a? String
|
101
|
+
raise "Unsupported object type: #{hnd.class}"
|
102
|
+
end
|
103
|
+
hnd = REXML::Document.new( hnd ) if hnd.is_a? IO or hnd.is_a? String
|
104
|
+
|
105
|
+
key = hnd.root.attributes['standard_key']
|
106
|
+
raise "Unsupported standard key: #{key}" if key == 'generic'
|
107
|
+
EDI::const_get(key)::const_get('Interchange').parse_xml( hnd )
|
108
|
+
# class_sym = (key+'Interchange').intern
|
109
|
+
# EDI::const_get(class_sym).parse_xml( hnd )
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def to_xml( xel_parent )
|
114
|
+
externalID = "PUBLIC\n" + " "*9
|
115
|
+
externalID += "'-//FH Wiesbaden FB DCSM//DTD XML Representation of EDI data V1.2//EN'\n"
|
116
|
+
externalID += " "*9
|
117
|
+
externalID += "'http://edi01.informatik.fh-wiesbaden.de/edi4r/edi4r-1.2.dtd'"
|
118
|
+
xel_parent << REXML::XMLDecl.new
|
119
|
+
xel_parent << REXML::DocType.new( normalized_class_name, externalID )
|
120
|
+
|
121
|
+
rc = super
|
122
|
+
|
123
|
+
xel = rc.first
|
124
|
+
pos = self.class.to_s =~ /EDI::((.*?)::)?Interchange/
|
125
|
+
raise "This is not an Interchange object: #{rc}!" unless pos==0
|
126
|
+
xel.attributes["standard_key"] = ($2 and not $2.empty?) ? $2 : "generic"
|
127
|
+
xel.attributes["version"] = @version
|
128
|
+
xel.attributes.delete "name"
|
129
|
+
rc
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
class MsgGroup
|
136
|
+
|
137
|
+
# Note: Code is very similar to Message.parse_xml. Remove redundancy?
|
138
|
+
|
139
|
+
def MsgGroup.parse_xml(p, xgrp)
|
140
|
+
_header = xgrp.elements["Header/Segment"]
|
141
|
+
_trailer = xgrp.elements["Trailer/Segment"]
|
142
|
+
grp = p.new_msggroup( Segment.parse_xml( p, _header ) )
|
143
|
+
|
144
|
+
grp.header = Segment.parse_xml( grp, _header ) if _header
|
145
|
+
xgrp.elements.each('Message') {|xel| grp.add Message.parse_xml(grp, xel)}
|
146
|
+
grp.trailer = Segment.parse_xml( grp, _trailer ) if _trailer
|
147
|
+
|
148
|
+
grp
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class Message
|
155
|
+
|
156
|
+
def Message.parse_xml(p, xmsg)
|
157
|
+
_header = xmsg.elements["Header/Segment"]
|
158
|
+
_trailer = xmsg.elements["Trailer/Segment"]
|
159
|
+
|
160
|
+
msg = p.new_message( Segment.parse_xml( p, _header ) )
|
161
|
+
msg.header = Segment.parse_xml( msg, _header ) if _header
|
162
|
+
|
163
|
+
xmsg.elements.each('descendant::Segment') do |xel|
|
164
|
+
next if xel.parent.name =~ /Header|Trailer/
|
165
|
+
msg.add Segment.parse_xml(msg, xel)
|
166
|
+
end
|
167
|
+
msg.trailer = Segment.parse_xml( msg, _trailer ) if _trailer
|
168
|
+
|
169
|
+
msg
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
# Build an XML document tree from
|
174
|
+
# a) the linear sequence of segments
|
175
|
+
# b) metadata from the standards DB (attached to each segment)
|
176
|
+
#
|
177
|
+
# Track xml parent element for segments by level
|
178
|
+
#
|
179
|
+
# Add 'header' and 'trailer' wrapper element around
|
180
|
+
# header and trailer, if any
|
181
|
+
#
|
182
|
+
# Trigger segments and their depending segments get wrapped
|
183
|
+
# in a 'SegmentGroup' element that bears the group name as its name.
|
184
|
+
|
185
|
+
def to_xml( xel_parent )
|
186
|
+
xel_msg = REXML::Element.new( 'Message' )
|
187
|
+
xel_parent.elements << xel_msg
|
188
|
+
|
189
|
+
# Default parent is XML message element itself
|
190
|
+
#
|
191
|
+
xel_parent_stack = Hash.new(xel_msg)
|
192
|
+
|
193
|
+
xhd = to_xml_header( xel_msg )
|
194
|
+
|
195
|
+
each do |seg|
|
196
|
+
next if seg.empty?
|
197
|
+
if seg.is_tnode?
|
198
|
+
xgrp = REXML::Element.new( 'SegmentGroup' )
|
199
|
+
xgrp.attributes["name"] = seg.sg_name
|
200
|
+
xel_parent_stack[seg.level - 1] << xgrp
|
201
|
+
seg.to_xml( xgrp )
|
202
|
+
xel_parent_stack[seg.level] = xgrp
|
203
|
+
else
|
204
|
+
seg.to_xml( xel_parent_stack[seg.level - 1] )
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
xtr = to_xml_trailer( xel_msg )
|
209
|
+
[xel_msg, xhd, xtr]
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
class Segment
|
216
|
+
|
217
|
+
def Segment.parse_xml( p, xseg )
|
218
|
+
tag = xseg.attributes['name']
|
219
|
+
seg = p.new_segment(tag)
|
220
|
+
xseg.elements.each('CDE') do |xcde|
|
221
|
+
cde_name = xcde.attributes['name']
|
222
|
+
i = (xcde.attributes['instance'] || 1).to_i - 1
|
223
|
+
cde = seg[cde_name][i]
|
224
|
+
Segment.parse_xml_de( cde, xcde )
|
225
|
+
end
|
226
|
+
Segment.parse_xml_de( seg, xseg )
|
227
|
+
seg
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def Segment.parse_xml_de( seg_or_cde, xseg_or_cde )
|
233
|
+
de_instance_counter = Hash.new(0)
|
234
|
+
xseg_or_cde.elements.each('DE') do |xde|
|
235
|
+
de_name = xde.attributes['name']
|
236
|
+
i = (xde.attributes['instance'] || 1).to_i - 1
|
237
|
+
seg_or_cde[de_name][i].parse( xde.text, true )
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
class DE
|
246
|
+
|
247
|
+
def to_xml( xel_parent, instance=nil )
|
248
|
+
xel = REXML::Element.new( 'DE' )
|
249
|
+
xel.attributes["name"] = @name
|
250
|
+
xel_parent.elements << xel
|
251
|
+
xel.text = self.to_s( true ) # don't escape!
|
252
|
+
xel
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
end # module EDI
|
@@ -0,0 +1,495 @@
|
|
1
|
+
# Classes providing access to directories of EDI standards like
|
2
|
+
# the UN Trade Data Interchange Directories (UNTDID) and Subsets,
|
3
|
+
# or ISO7389, or SAP IDoc definitions.
|
4
|
+
#
|
5
|
+
# Part of the EDI module "edi4r", a class library
|
6
|
+
# to parse and create UN/EDIFACT and other EDI data
|
7
|
+
#
|
8
|
+
# :include: ../../AuthorCopyright
|
9
|
+
#
|
10
|
+
# $Id: standards.rb,v 1.9 2006/08/01 11:00:35 werntges Exp $
|
11
|
+
#--
|
12
|
+
# $Log: standards.rb,v $
|
13
|
+
# Revision 1.9 2006/08/01 11:00:35 werntges
|
14
|
+
# EDI_NDB_PATH: Now platform independent
|
15
|
+
#
|
16
|
+
# Revision 1.8 2006/05/26 16:55:38 werntges
|
17
|
+
# V 0.9.3 snapshot. RDoc added, some renaming & cleanup.
|
18
|
+
#
|
19
|
+
# Revision 1.7 2006/04/28 14:26:30 werntges
|
20
|
+
# 0.9.1 snapshot
|
21
|
+
#
|
22
|
+
# Revision 1.6 2006/03/28 22:21:35 werntges
|
23
|
+
# changed to symbols as parameter keys, e.g. "d0051" to :d0051
|
24
|
+
#
|
25
|
+
# Revision 1.5 2006/03/22 16:51:29 werntges
|
26
|
+
# snapshot after edi4r-0.8.2.gem
|
27
|
+
#
|
28
|
+
# Revision 1.4 2004/02/19 17:33:34 heinz
|
29
|
+
# HWW: Snapshot after REMADV mapping (no changes here, just testing)
|
30
|
+
#
|
31
|
+
# Revision 1.3 2004/02/14 12:11:03 heinz
|
32
|
+
# HWW: Minor improvements
|
33
|
+
#
|
34
|
+
# Revision 1.2 2004/02/11 23:35:35 heinz
|
35
|
+
# HWW: Caching bug fixed, each_BCDS_entry revised, IDoc support added
|
36
|
+
#
|
37
|
+
# Revision 1.1 2004/02/09 16:31:13 heinz
|
38
|
+
# Initial revision
|
39
|
+
#
|
40
|
+
# To-do list:
|
41
|
+
# all - Major refactoring would be a good idea... just starting this ;-)
|
42
|
+
# err - More error classes, maintained separately
|
43
|
+
#++
|
44
|
+
|
45
|
+
# Module EDI::Dir
|
46
|
+
# Collection of Structs and classes for EDI Directory management
|
47
|
+
|
48
|
+
module EDI
|
49
|
+
module Dir
|
50
|
+
|
51
|
+
DE_Properties = Struct.new( :name, :format, :status, :dummy, :description)
|
52
|
+
|
53
|
+
# Common structure of B)ranch (of a msg), C)DE, D)E, S)egment
|
54
|
+
#
|
55
|
+
BCDS_entry = Struct.new( :item_no, :name, :status, :maxrep)
|
56
|
+
|
57
|
+
# Named_list:
|
58
|
+
#
|
59
|
+
# A simplified Array to represent objects of EDI classes CDE, Segment, and
|
60
|
+
# Message (branches) as lists of their constituting sub-units, augmented
|
61
|
+
# by the common properties +name+ and +desc+ (description).
|
62
|
+
#
|
63
|
+
class Named_list
|
64
|
+
attr_accessor :name, :desc
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@name, @desc, @store = nil, nil, []
|
68
|
+
end
|
69
|
+
|
70
|
+
def <<(obj)
|
71
|
+
@store << obj
|
72
|
+
end
|
73
|
+
|
74
|
+
def each( &b )
|
75
|
+
@store.each( &b )
|
76
|
+
end
|
77
|
+
|
78
|
+
def size
|
79
|
+
@store.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def empty?
|
83
|
+
@store.empty?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# A Directory object is currently a set of hashes representing
|
89
|
+
# all the entries for data elements, composites, segments, and messages.
|
90
|
+
#
|
91
|
+
class Directory
|
92
|
+
#
|
93
|
+
# Some ingredients for Directory caching:
|
94
|
+
#
|
95
|
+
@@cache = {}
|
96
|
+
@@caching = true
|
97
|
+
private_class_method :new
|
98
|
+
|
99
|
+
# As long as we employ plain CSV files to store directories, a Directory
|
100
|
+
# can become quite memory-consuming.
|
101
|
+
# Therefore Directorys are cached after creation, so that they
|
102
|
+
# need to be created and maintained only once when there areeseveral
|
103
|
+
# messages of the same type in an interchange.
|
104
|
+
#
|
105
|
+
# Turns off this caching mechanism, saving memory but costing time.
|
106
|
+
#
|
107
|
+
def Directory.caching_off
|
108
|
+
@@caching = false
|
109
|
+
end
|
110
|
+
|
111
|
+
# Turns on caching (default setting), saving time but costing memory.
|
112
|
+
#
|
113
|
+
def Directory.caching_on
|
114
|
+
@@caching = true
|
115
|
+
end
|
116
|
+
|
117
|
+
# Tells if caching is currently activated (returns a boolean)
|
118
|
+
#
|
119
|
+
def Directory.caching?
|
120
|
+
@@caching
|
121
|
+
end
|
122
|
+
|
123
|
+
# Releases memory by flushing the cache. Needed primarily for unit tests,
|
124
|
+
# where many if not all available diagrams are created.
|
125
|
+
#
|
126
|
+
def Directory.flush_cache
|
127
|
+
@@cache = {}
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# Creates (and caches) a new directory. Returns reference to
|
132
|
+
# existing directory when already in cache.
|
133
|
+
#
|
134
|
+
# std:: The syntax standard key. Currently supported:
|
135
|
+
# - 'E' (EDIFACT),
|
136
|
+
# - 'I' (SAP IDOC)
|
137
|
+
# params:: A hash of parameters that uniquely identify the selected dir.
|
138
|
+
# Hash parameters use following alternative key sets:
|
139
|
+
#
|
140
|
+
# ISO9735:: :d0002, :d0076 (default: "", nil)
|
141
|
+
# UN/TDID:: :d0065, :d0052, :d0054, :d0051, :d0057; :is_iedi
|
142
|
+
# SAP IDOC:: :IDOCTYPE, :SAPTYPE, :EXTENSION (see EDI_DC fields)
|
143
|
+
#
|
144
|
+
# UN/TDID: Elements of S009 or S320 are used:
|
145
|
+
# d0065:: Message type like "INVOIC"
|
146
|
+
# d0052:: Message version number, like "90" or "D"
|
147
|
+
# d0054:: Message release number, like "1" or "03A"
|
148
|
+
# d0051:: Controlling agency, like "UN" or "EN"
|
149
|
+
# d0057:: Association assigned code (optional), like "EAN008"
|
150
|
+
#
|
151
|
+
# Interactive EDI (only limited supported so far):
|
152
|
+
# is_iedi:: Flag, +true+ or +false+. Assumed +false+ if missing.
|
153
|
+
#
|
154
|
+
def Directory.create( std, params )
|
155
|
+
|
156
|
+
case std
|
157
|
+
when 'E' # UN/EDIFACT
|
158
|
+
par = {:d0051 => '',
|
159
|
+
:d0057 => '',
|
160
|
+
:is_iedi => false }.update( params )
|
161
|
+
when 'I' # SAP IDocs
|
162
|
+
par = { }.update( params )
|
163
|
+
else
|
164
|
+
raise "Unsupported syntax standard: #{std}"
|
165
|
+
end
|
166
|
+
|
167
|
+
if Directory.caching?
|
168
|
+
|
169
|
+
# Use param set as key for caching
|
170
|
+
#
|
171
|
+
key = par.sort {|a,b| a.to_s <=> b.to_s}.hash
|
172
|
+
obj = @@cache[key]
|
173
|
+
return obj unless obj.nil?
|
174
|
+
|
175
|
+
obj = new( std, par )
|
176
|
+
@@cache[key] = obj # cache & return it
|
177
|
+
|
178
|
+
else
|
179
|
+
new( std, par )
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# Helper method: Derive path fragments of CSV files from parameters
|
185
|
+
#
|
186
|
+
def Directory.prefix_ext_finder( std, par )
|
187
|
+
ext = ''
|
188
|
+
case std
|
189
|
+
|
190
|
+
when 'I' # SAP IDocs
|
191
|
+
prefix = '/sap'
|
192
|
+
if par[:IDOCTYPE]
|
193
|
+
prefix += '/idocs'+par[:SAPTYPE]+'/'+par[:IDOCTYPE]+'/'
|
194
|
+
if par[:EXTENSION].is_a? String and not par[:EXTENSION].empty?
|
195
|
+
if par[:EXTENSION] =~ /\/(.*\/)([^\/]+)/
|
196
|
+
prefix += $1 + 'ED'
|
197
|
+
ext = $2 + '.csv'
|
198
|
+
else
|
199
|
+
prefix += 'ED'
|
200
|
+
ext = par[:EXTENSION] + '.csv'
|
201
|
+
end
|
202
|
+
else
|
203
|
+
prefix += 'ED'
|
204
|
+
ext = '.csv'
|
205
|
+
end
|
206
|
+
else
|
207
|
+
case par[:SAPTYPE]
|
208
|
+
when '40'; ext = '04000'
|
209
|
+
else ; raise "Unsupported SAP Type: #{par[:SAPTYPE]}"
|
210
|
+
end
|
211
|
+
prefix += '/controls/SD'
|
212
|
+
ext += '.csv'
|
213
|
+
end
|
214
|
+
|
215
|
+
when 'E' # UN/EDIFACT
|
216
|
+
prefix = '/edifact'
|
217
|
+
if par[:d0002] # ISO9735 requested?
|
218
|
+
case par[:d0002]
|
219
|
+
when 1
|
220
|
+
ext = '10000'
|
221
|
+
when 2
|
222
|
+
ext = '20000'
|
223
|
+
when 3
|
224
|
+
ext = '30000'
|
225
|
+
when 4
|
226
|
+
# Assume that any setting of d0076 implies SV 4-1
|
227
|
+
# Revise when SV 4-2 arrives!
|
228
|
+
ext = (par[:d0076] == nil) ? '40000' : '40100'
|
229
|
+
else
|
230
|
+
raise "Invalid syntax version: #{par[:d0002]}"
|
231
|
+
end
|
232
|
+
prefix += '/iso9735/SD'
|
233
|
+
ext += '.csv'
|
234
|
+
|
235
|
+
else # UN/TDID requested?
|
236
|
+
prefix += par[:is_iedi] ? '/untdid/ID' : '/untdid/ED'
|
237
|
+
ext = (par[:d0052]+par[:d0054]).downcase + '.csv'
|
238
|
+
end
|
239
|
+
|
240
|
+
else
|
241
|
+
raise "Unsupported syntax standard: #{std}"
|
242
|
+
end
|
243
|
+
|
244
|
+
return prefix, ext
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
#
|
249
|
+
# Helper method: Determine path of requested csv file
|
250
|
+
#
|
251
|
+
# Will be generalized to a lookup scheme!
|
252
|
+
#
|
253
|
+
def Directory.path_finder( prefix, ext, selector )
|
254
|
+
filename = prefix + selector + '.' + ext
|
255
|
+
searchpath = ENV['EDI_NDB_PATH']
|
256
|
+
|
257
|
+
searchpath.split(/#{File::PATH_SEPARATOR}/).each do |datadir|
|
258
|
+
path = datadir + filename
|
259
|
+
return path if File.readable? path
|
260
|
+
end
|
261
|
+
raise "No readable file '." + filename + "' found below any dir on '" + searchpath + "'"
|
262
|
+
end
|
263
|
+
|
264
|
+
#
|
265
|
+
# see Directory.create
|
266
|
+
#
|
267
|
+
def initialize ( std, par ) # :nodoc:
|
268
|
+
|
269
|
+
prefix, ext = Directory.prefix_ext_finder( std, par )
|
270
|
+
|
271
|
+
# Build DE directory
|
272
|
+
|
273
|
+
prefix_ed = prefix.sub(/ID$/, 'ED') # There is no IDED.*.csv!
|
274
|
+
csvFileName = Directory.path_finder(prefix_ed, ext, 'ED' )
|
275
|
+
@de_dir = Hash.new
|
276
|
+
IO.foreach(csvFileName) do |line|
|
277
|
+
d = DE_Properties.new
|
278
|
+
d.name, d.format, d.dummy, d.description = line.strip.split(/;/)
|
279
|
+
$stderr.puts "ERR DE line", line if d.description.nil?
|
280
|
+
@de_dir[d.name] = d
|
281
|
+
end
|
282
|
+
|
283
|
+
# Build CDE directory
|
284
|
+
|
285
|
+
csvFileName = Directory.path_finder(prefix, ext, 'CD' )
|
286
|
+
@cde_dir = Hash.new
|
287
|
+
IO.foreach(csvFileName) do |line|
|
288
|
+
c = Named_list.new
|
289
|
+
c.name, c.desc, list = line.split(/;/, 3)
|
290
|
+
$stderr.puts "ERR CDE line", line if list.nil?
|
291
|
+
list.sub(/;\s*$/,'').split(/;/).each_slice(4) do |item, code, status, fmt|
|
292
|
+
$stderr.puts "ERR CDE list", line if fmt.nil?
|
293
|
+
c << BCDS_entry.new( item, code, status, 1 )
|
294
|
+
end
|
295
|
+
@cde_dir[c.name] = c
|
296
|
+
end
|
297
|
+
|
298
|
+
# Build Segment directory
|
299
|
+
|
300
|
+
csvFileName = Directory.path_finder(prefix, ext, 'SD' )
|
301
|
+
@seg_dir = Hash.new
|
302
|
+
IO.foreach(csvFileName) do |line|
|
303
|
+
c = Named_list.new
|
304
|
+
c.name, c.desc, list = line.split(/;/, 3)
|
305
|
+
$stderr.puts "ERR SEG line", line if list.nil?
|
306
|
+
list.sub(/;\s*$/,'').split(/;/).each_slice(4) do |item, code, status, maxrep|
|
307
|
+
$stderr.puts "ERR SEG list", line if maxrep.nil?
|
308
|
+
c << BCDS_entry.new( item, code, status, maxrep.to_i )
|
309
|
+
end
|
310
|
+
@seg_dir[c.name] = c
|
311
|
+
end
|
312
|
+
|
313
|
+
# Build Message directory
|
314
|
+
|
315
|
+
csvFileName = Directory.path_finder(prefix, ext, 'MD' )
|
316
|
+
@msg_dir = Hash.new
|
317
|
+
re = if par[:d0065] and par[:d0065] =~ /([A-Z]{6})/
|
318
|
+
then Regexp.new($1) else nil end
|
319
|
+
IO.foreach(csvFileName) do |line|
|
320
|
+
next if re and line !~ re # Only lines matching message type if given
|
321
|
+
c = Named_list.new
|
322
|
+
c.name, c.desc, list = line.split(/;/, 3)
|
323
|
+
$stderr.puts "ERR MSG line", line if list.nil?
|
324
|
+
list.sub(/;\s*$/,'').split(/;/).each_slice(3) do |code, status, maxrep|
|
325
|
+
$stderr.puts "ERR MSG list", line if maxrep.nil?
|
326
|
+
c << BCDS_entry.new( "0000", code, status, maxrep.to_i )
|
327
|
+
end
|
328
|
+
@msg_dir[c.name] = c
|
329
|
+
end
|
330
|
+
end # initialize
|
331
|
+
|
332
|
+
|
333
|
+
# Returns CSV line for DE called +name+.
|
334
|
+
# If +name+ is a Regexp, returns the first match or +nil+.
|
335
|
+
#
|
336
|
+
def de( name )
|
337
|
+
if name.is_a? Regexp
|
338
|
+
@de_dir[ @de_dir.keys.find {|key| key =~ name} ]
|
339
|
+
else
|
340
|
+
@de_dir[name]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns a sorted list of names of available DE
|
345
|
+
#
|
346
|
+
def de_names
|
347
|
+
@de_dir.keys.sort
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
# Returns CSV line for CDE called +name+.
|
352
|
+
#
|
353
|
+
def cde( name )
|
354
|
+
@cde_dir[name]
|
355
|
+
end
|
356
|
+
|
357
|
+
# Returns a sorted list of names of available CDE
|
358
|
+
#
|
359
|
+
def cde_names
|
360
|
+
@cde_dir.keys.sort
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
# Returns CSV line for segment called +name+.
|
365
|
+
# If +name+ is a Regexp, returns the first match or +nil+.
|
366
|
+
#
|
367
|
+
def segment( name )
|
368
|
+
if name.is_a? Regexp
|
369
|
+
@seg_dir[ @seg_dir.keys.find {|key| key =~ name} ]
|
370
|
+
else
|
371
|
+
@seg_dir[name]
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# Returns a sorted list of names of available segments
|
376
|
+
#
|
377
|
+
def segment_names
|
378
|
+
@seg_dir.keys.sort
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
# Returns CSV line of top branch for message called +name+.
|
383
|
+
#
|
384
|
+
def message( name ) # Actually, only one branch!
|
385
|
+
# $stderr.puts name
|
386
|
+
@msg_dir[name]
|
387
|
+
end
|
388
|
+
|
389
|
+
# Returns a sorted list of names of available messages
|
390
|
+
#
|
391
|
+
def message_names
|
392
|
+
@msg_dir.keys.sort
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
# Iterates over each branch (message), composite, data element,
|
397
|
+
# or segment found (hence: BCDS) that is matched by +id+.
|
398
|
+
#
|
399
|
+
# +id+ is a string. The object type requested by this string is not
|
400
|
+
# obvious. This method determines it through a naming convention.
|
401
|
+
# See source for details.
|
402
|
+
#
|
403
|
+
# Fails with EDI::EDILookupError when nothing found.
|
404
|
+
|
405
|
+
def each_BCDS( id, &b )
|
406
|
+
list = nil
|
407
|
+
case id
|
408
|
+
when /^[CES]\d{3}$/ # C)omposite
|
409
|
+
list = cde(id)
|
410
|
+
|
411
|
+
when /^\d{4}$/ # Simple D)E
|
412
|
+
list = de(id)
|
413
|
+
|
414
|
+
when /^[A-Z]{3}$/ # S)egment
|
415
|
+
list = segment(id)
|
416
|
+
|
417
|
+
when /^[A-Z]{6}:$/ # Message B)ranch
|
418
|
+
list = message(id)
|
419
|
+
|
420
|
+
# Workaround for the IDoc case:
|
421
|
+
# We identify entry type by a (intermediate) prefix
|
422
|
+
#
|
423
|
+
when /^d(.*)$/ # Simple D)E
|
424
|
+
list = de($1)
|
425
|
+
|
426
|
+
when /^s(.*)$/ # S)egment, SAP IDOC
|
427
|
+
list = segment($1)
|
428
|
+
|
429
|
+
when /^m(.*)$/ # Message B)ranch
|
430
|
+
list = message($1)
|
431
|
+
|
432
|
+
else # Should never occur
|
433
|
+
raise IndexError, "Not a legal BCDS entry id: '#{id}'"
|
434
|
+
end
|
435
|
+
|
436
|
+
raise EDILookupError, "#{id} not in directory!" if list.nil?
|
437
|
+
list.each( &b )
|
438
|
+
end
|
439
|
+
|
440
|
+
end # Directory
|
441
|
+
|
442
|
+
end # module Dir
|
443
|
+
|
444
|
+
# Special Exception class that sometimes gets rescued
|
445
|
+
#
|
446
|
+
class EDILookupError < IndexError
|
447
|
+
end
|
448
|
+
|
449
|
+
end # module EDI
|
450
|
+
|
451
|
+
|
452
|
+
# :enddoc:
|
453
|
+
if __FILE__ == $0
|
454
|
+
# Test code
|
455
|
+
|
456
|
+
require 'enumerator'
|
457
|
+
require 'pathname'
|
458
|
+
|
459
|
+
# Make this file standalone during testing:
|
460
|
+
ENV['EDI_NDB_PATH'] =
|
461
|
+
Pathname.new(__FILE__).parent.parent.parent.to_s+'/data'
|
462
|
+
|
463
|
+
# EDIFACT tests
|
464
|
+
|
465
|
+
d = EDI::Dir::Directory.create('E',
|
466
|
+
:d0065 => 'ORDERS',
|
467
|
+
:d0052 =>'D',
|
468
|
+
:d0054 =>'96A')
|
469
|
+
i = EDI::Dir::Directory.create('E', :d0002 => 2)
|
470
|
+
|
471
|
+
puts i.de_names; gets
|
472
|
+
puts i.cde_names; gets
|
473
|
+
puts d.message_names; gets
|
474
|
+
puts d.de('4457'); gets
|
475
|
+
|
476
|
+
# EDIFACT bulk tests
|
477
|
+
|
478
|
+
d = EDI::Dir::Directory.create('E',
|
479
|
+
:d0052 =>'D',
|
480
|
+
:d0054 =>'96A')
|
481
|
+
|
482
|
+
puts d.message_names; gets
|
483
|
+
|
484
|
+
# SAP IDOC tests (should fail now!)
|
485
|
+
|
486
|
+
s = EDI::Dir::Directory.create('I',
|
487
|
+
:SAPTYPE => '40',
|
488
|
+
:IDOCTYPE => 'ORDERS04')
|
489
|
+
t = EDI::Dir::Directory.create('I',
|
490
|
+
:SAPTYPE => '40',
|
491
|
+
:IDOCTYPE => 'ORDERS05',
|
492
|
+
:EXTENSION => '/GIL/EPG_ORDERS05')
|
493
|
+
puts s.de_names
|
494
|
+
puts t.de_names
|
495
|
+
end
|