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.
Files changed (62) hide show
  1. data/AuthorCopyright +10 -0
  2. data/COPYING +56 -0
  3. data/ChangeLog +106 -0
  4. data/README +66 -0
  5. data/TO-DO +35 -0
  6. data/Tutorial +609 -0
  7. data/VERSION +1 -0
  8. data/bin/edi2xml.rb +103 -0
  9. data/bin/editool.rb +151 -0
  10. data/bin/xml2edi.rb +50 -0
  11. data/data/edifact/iso9735/SDCD.10000.csv +10 -0
  12. data/data/edifact/iso9735/SDCD.20000.csv +10 -0
  13. data/data/edifact/iso9735/SDCD.30000.csv +11 -0
  14. data/data/edifact/iso9735/SDCD.40000.csv +31 -0
  15. data/data/edifact/iso9735/SDCD.40100.csv +31 -0
  16. data/data/edifact/iso9735/SDED.10000.csv +37 -0
  17. data/data/edifact/iso9735/SDED.20000.csv +37 -0
  18. data/data/edifact/iso9735/SDED.30000.csv +43 -0
  19. data/data/edifact/iso9735/SDED.40000.csv +129 -0
  20. data/data/edifact/iso9735/SDED.40100.csv +130 -0
  21. data/data/edifact/iso9735/SDMD.10000.csv +0 -0
  22. data/data/edifact/iso9735/SDMD.20000.csv +0 -0
  23. data/data/edifact/iso9735/SDMD.30000.csv +6 -0
  24. data/data/edifact/iso9735/SDMD.40000.csv +17 -0
  25. data/data/edifact/iso9735/SDMD.40100.csv +17 -0
  26. data/data/edifact/iso9735/SDSD.10000.csv +8 -0
  27. data/data/edifact/iso9735/SDSD.20000.csv +8 -0
  28. data/data/edifact/iso9735/SDSD.30000.csv +12 -0
  29. data/data/edifact/iso9735/SDSD.40000.csv +34 -0
  30. data/data/edifact/iso9735/SDSD.40100.csv +34 -0
  31. data/data/edifact/untdid/EDCD.d01b.csv +200 -0
  32. data/data/edifact/untdid/EDCD.d96a.csv +161 -0
  33. data/data/edifact/untdid/EDED.d01b.csv +641 -0
  34. data/data/edifact/untdid/EDED.d96a.csv +462 -0
  35. data/data/edifact/untdid/EDMD.d01b.csv +3419 -0
  36. data/data/edifact/untdid/EDMD.d96a.csv +2144 -0
  37. data/data/edifact/untdid/EDSD.d01b.csv +158 -0
  38. data/data/edifact/untdid/EDSD.d96a.csv +127 -0
  39. data/data/edifact/untdid/IDCD.d01b.csv +95 -0
  40. data/data/edifact/untdid/IDMD.d01b.csv +238 -0
  41. data/data/edifact/untdid/IDSD.d01b.csv +75 -0
  42. data/lib/edi4r.rb +928 -0
  43. data/lib/edi4r/diagrams.rb +567 -0
  44. data/lib/edi4r/edi4r-1.2.dtd +20 -0
  45. data/lib/edi4r/edifact-rexml.rb +221 -0
  46. data/lib/edi4r/edifact.rb +1627 -0
  47. data/lib/edi4r/rexml.rb +256 -0
  48. data/lib/edi4r/standards.rb +495 -0
  49. data/test/eancom2webedi.rb +380 -0
  50. data/test/groups.edi +1 -0
  51. data/test/in1.edi +1 -0
  52. data/test/in1.inh +3 -0
  53. data/test/in2.edi +1 -0
  54. data/test/in2.xml +350 -0
  55. data/test/test_basics.rb +209 -0
  56. data/test/test_edi_split.rb +53 -0
  57. data/test/test_loopback.rb +21 -0
  58. data/test/test_minidemo.rb +84 -0
  59. data/test/test_rexml.rb +98 -0
  60. data/test/test_tut_examples.rb +131 -0
  61. data/test/webedi2eancom.rb +408 -0
  62. metadata +110 -0
@@ -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