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
@@ -0,0 +1,567 @@
|
|
1
|
+
# Classes related to message diagrams and validation
|
2
|
+
# for the EDI module "edi4r", a class library
|
3
|
+
# to parse and create UN/EDIFACT and other EDI data
|
4
|
+
#
|
5
|
+
# :include: ../../AuthorCopyright
|
6
|
+
#
|
7
|
+
# $Id: diagrams.rb,v 1.8 2006/05/26 16:57:37 werntges Exp $
|
8
|
+
#--
|
9
|
+
# $Log: diagrams.rb,v $
|
10
|
+
# Revision 1.8 2006/05/26 16:57:37 werntges
|
11
|
+
# V 0.9.3 snapshot. RDoc added, some refactoring / renaming / cleanups
|
12
|
+
#
|
13
|
+
# Revision 1.7 2006/04/28 14:25:42 werntges
|
14
|
+
# 0.9.1 snapshot
|
15
|
+
#
|
16
|
+
# Revision 1.6 2006/03/28 22:24:58 werntges
|
17
|
+
# changed from strings to symbols as parameter keys, e.g. :d0051
|
18
|
+
#
|
19
|
+
# Revision 1.5 2006/03/22 16:57:17 werntges
|
20
|
+
# id --> object_id
|
21
|
+
#
|
22
|
+
# Revision 1.4 2004/02/19 17:34:58 heinz
|
23
|
+
# HWW: Snapshot after REMADV mapping
|
24
|
+
#
|
25
|
+
# Revision 1.3 2004/02/11 23:39:14 heinz
|
26
|
+
# HWW: IDoc support added, lots of dead code/data removed,
|
27
|
+
# caching bug fixed, seek! augmented to accept also Regexp, ...
|
28
|
+
#
|
29
|
+
# Revision 1.2 2004/02/10 18:17:52 heinz
|
30
|
+
# HWW: Temp. check-in, before adding support for multiple standards
|
31
|
+
#
|
32
|
+
# Revision 1.1 2003/10/22 16:56:30 heinz
|
33
|
+
# Initial revision
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# To-do list:
|
37
|
+
# all - Just starting this...
|
38
|
+
#++
|
39
|
+
#
|
40
|
+
# Module "EDI::Diagram" bundles the classes needed to maintain
|
41
|
+
# branching diagrams, i.e. data structures that formally
|
42
|
+
# define message types.
|
43
|
+
|
44
|
+
module EDI::Diagram
|
45
|
+
|
46
|
+
#
|
47
|
+
# Diagram: A structure class to represent a message diagram (branching diagram)
|
48
|
+
#
|
49
|
+
# A Diagram is essentially
|
50
|
+
# - a Branch object (at top-level)
|
51
|
+
# - a description text
|
52
|
+
# - a node dictionary (a hash that allows index-like access)
|
53
|
+
#
|
54
|
+
# In contrast to a simple Branch, all nodes of a Diagram object
|
55
|
+
# are indexed (counting from 1) according to their natural sequence
|
56
|
+
# in the EDIFACT branching diagram. Thus, access by index is available
|
57
|
+
# for Diagram objects, but not for Branch objects.
|
58
|
+
#
|
59
|
+
|
60
|
+
class Diagram
|
61
|
+
@@cache = {}
|
62
|
+
@@caching = true
|
63
|
+
private_class_method :new
|
64
|
+
|
65
|
+
# A Diagram can become quite complex and memory-consuming.
|
66
|
+
# Therefore diagrams are cached after creation, so that they
|
67
|
+
# need to be created and maintained only once when there are several
|
68
|
+
# messages of the same type in an interchange.
|
69
|
+
#
|
70
|
+
# Turns off this caching mechanism, saving memory but costing time.
|
71
|
+
#
|
72
|
+
def Diagram.caching_off
|
73
|
+
@@caching = false
|
74
|
+
end
|
75
|
+
|
76
|
+
# Turns on caching (default setting), saving time but costing memory.
|
77
|
+
#
|
78
|
+
def Diagram.caching_on
|
79
|
+
@@caching = true
|
80
|
+
end
|
81
|
+
|
82
|
+
# Tells if caching is currently activated (returns a boolean)
|
83
|
+
#
|
84
|
+
def Diagram.caching?
|
85
|
+
@@caching
|
86
|
+
end
|
87
|
+
|
88
|
+
# Releases memory by flushing the cache. Needed primarily for unit tests,
|
89
|
+
# where many if not all available diagrams are created.
|
90
|
+
|
91
|
+
def Diagram.flush_cache
|
92
|
+
@@cache = {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Creates (and caches) a new diagram. Returns reference to existing diagram
|
96
|
+
# when already in cache.
|
97
|
+
# std:: The syntax standard key. Currently supported:
|
98
|
+
# - 'E' (EDIFACT),
|
99
|
+
# - 'I' (SAP IDOC)
|
100
|
+
# params:: A hash of parameters that uniquely identify the selected diagram.
|
101
|
+
# Internal use only - see source code for details.
|
102
|
+
#
|
103
|
+
def Diagram.create( std, params )
|
104
|
+
case std
|
105
|
+
when 'E' # UN/EDIFACT
|
106
|
+
par = {:d0051 => 'UN',
|
107
|
+
# :d0057 => '',
|
108
|
+
:is_iedi => false }.update( params )
|
109
|
+
when 'I' # SAP IDocs
|
110
|
+
par = params
|
111
|
+
# raise "Not implemented yet!"
|
112
|
+
else
|
113
|
+
raise "Unsupported syntax standard: #{std}"
|
114
|
+
end
|
115
|
+
|
116
|
+
if Diagram.caching?
|
117
|
+
#
|
118
|
+
# Use param set as key for caching
|
119
|
+
#
|
120
|
+
key = par.sort {|a,b| a.to_s <=> b.to_s}.hash
|
121
|
+
obj = @@cache[key]
|
122
|
+
return obj unless obj.nil?
|
123
|
+
|
124
|
+
obj = new( std, par )
|
125
|
+
@@cache[key] = obj # cache & return it
|
126
|
+
|
127
|
+
else
|
128
|
+
new( std, par )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def initialize( std, par ) # :nodoc:
|
134
|
+
case std
|
135
|
+
when 'E' # UN/EDIFACT
|
136
|
+
@base_key = [par[:d0065], # msg type
|
137
|
+
par[:d0052], # version
|
138
|
+
par[:d0054], # release,
|
139
|
+
par[:d0051], # resp. agency
|
140
|
+
'',
|
141
|
+
# par[:d0057], # assoc. assigned code (subset)
|
142
|
+
''].join(':')
|
143
|
+
@msg_type = par[:d0065]
|
144
|
+
|
145
|
+
@dir = EDI::Dir::Directory.create(std,
|
146
|
+
:d0065 => @msg_type,
|
147
|
+
:d0052 => par[:d0052],
|
148
|
+
:d0054 => par[:d0054],
|
149
|
+
:d0051 => par[:d0051],
|
150
|
+
:d0057 => par[:d0057],
|
151
|
+
:is_iedi => par[:is_iedi])
|
152
|
+
when 'I' # SAP IDocs
|
153
|
+
@base_key = [par[:IDOCTYPE],
|
154
|
+
par[:EXTENSION],
|
155
|
+
par[:SAPTYPE],
|
156
|
+
'',
|
157
|
+
'',
|
158
|
+
# par[:d0057], # assoc. assigned code (subset)
|
159
|
+
''].join(':')
|
160
|
+
@msg_type = par[:IDOCTYPE]
|
161
|
+
|
162
|
+
@dir = EDI::Dir::Directory.create(std,
|
163
|
+
:DOCTYPE => @msg_type,
|
164
|
+
:EXTENSION => par[:EXTENSION],
|
165
|
+
:SAPTYPE => par[:SAPTYPE])
|
166
|
+
# raise "Not implemented yet!"
|
167
|
+
else
|
168
|
+
raise "Unsupported syntax standard: #{std}"
|
169
|
+
end
|
170
|
+
|
171
|
+
top_branch = Branch.new( @base_key, nil, self )
|
172
|
+
raise "No branch found for key '#{@base_key}'" unless top_branch
|
173
|
+
|
174
|
+
@diag = top_branch.expand
|
175
|
+
@desc = @diag.desc
|
176
|
+
|
177
|
+
i = 0; @node_dict = {}
|
178
|
+
@diag.each { |node| i+=1; node.index=i; @node_dict[i]=node }
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
# Iterates recursively through all nodes of the diagram.
|
183
|
+
#
|
184
|
+
def each(&b)
|
185
|
+
@diag.each(&b)
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# Index access through ordinal number of node, starting with 1 (!).
|
190
|
+
#
|
191
|
+
def [](i)
|
192
|
+
@node_dict[i] # efficient access via hash
|
193
|
+
end
|
194
|
+
|
195
|
+
# Getter for the directory object associated with this diagram.
|
196
|
+
#
|
197
|
+
def dir
|
198
|
+
@dir
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns the top branch of the diagram.
|
202
|
+
#
|
203
|
+
def branch
|
204
|
+
@diag
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
#
|
211
|
+
# A Branch is a sequence of Nodes. It corresponds to a segment group without
|
212
|
+
# its included groups (no sub-branches). A Branch has a name (+sg_name+)
|
213
|
+
# and comes with descriptory text (+desc+).
|
214
|
+
#
|
215
|
+
# Note that included TNodes may have side chains/branches ("tails").
|
216
|
+
#
|
217
|
+
class Branch
|
218
|
+
attr_accessor :desc
|
219
|
+
attr_reader :sg_name
|
220
|
+
|
221
|
+
# A new Branch object is uniquely identified by the +key+ argument
|
222
|
+
# that selects the directory entry and its +sg_name+ (if nto top branch).
|
223
|
+
# +root+ is a reference to the Diagram it belongs to.
|
224
|
+
|
225
|
+
def initialize(key, sg_name, root)
|
226
|
+
# puts "Creating branch for key `#{key+sg_name}'..."
|
227
|
+
@key = key
|
228
|
+
@sg_name = sg_name
|
229
|
+
@root = root
|
230
|
+
|
231
|
+
@nodelist=[]
|
232
|
+
b = @root.dir.message( key+sg_name.to_s )
|
233
|
+
raise "Lookup failed for key `#{key+sg_name.to_s}'" unless b
|
234
|
+
@desc = b.desc
|
235
|
+
b.each {|obj| @nodelist << Node.create( obj.name, obj.status, obj.maxrep )}
|
236
|
+
end
|
237
|
+
|
238
|
+
#
|
239
|
+
# Recursively add "tails" (branches, segment groups) to TNodes
|
240
|
+
#
|
241
|
+
def expand
|
242
|
+
each do |node|
|
243
|
+
if node.is_a? TNode and node.tail == nil
|
244
|
+
# puts "Expanding #{node}"
|
245
|
+
tail = Branch.new(@key, node.name, @root)
|
246
|
+
|
247
|
+
# Merge TNode with first tail node (trigger segment)
|
248
|
+
trigger_segment = tail.shift
|
249
|
+
node.name = trigger_segment.name
|
250
|
+
if trigger_segment.status != 'M' or trigger_segment.maxrep != 1
|
251
|
+
raise "#{trigger_segment.name}: Not a trigger seg!"
|
252
|
+
end
|
253
|
+
node.tail = tail.expand # Recursion!
|
254
|
+
end
|
255
|
+
end
|
256
|
+
self
|
257
|
+
end
|
258
|
+
|
259
|
+
# Removes and returns the first node from the node list, cf. Array#shift
|
260
|
+
#
|
261
|
+
def shift
|
262
|
+
@nodelist.shift
|
263
|
+
end
|
264
|
+
|
265
|
+
# Makes +node+ the first node of the node list, cf. Array#unshift
|
266
|
+
#
|
267
|
+
def unshift(node)
|
268
|
+
@nodelist.unshift(node)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Iterate through each node of the node list
|
272
|
+
#
|
273
|
+
def each
|
274
|
+
@nodelist.each {|node|
|
275
|
+
yield(node)
|
276
|
+
if node.is_a? TNode and node.tail
|
277
|
+
node.tail.each {|tn| yield(tn)} # Recursion
|
278
|
+
end
|
279
|
+
}
|
280
|
+
end
|
281
|
+
|
282
|
+
# def each_pruned
|
283
|
+
# @nodelist.each {|node|
|
284
|
+
# yield(node) # Simple array interator, no sidechains
|
285
|
+
# }
|
286
|
+
# end
|
287
|
+
|
288
|
+
# Access node list by index, cf. Array
|
289
|
+
#
|
290
|
+
def [](index)
|
291
|
+
@nodelist[index]
|
292
|
+
end
|
293
|
+
|
294
|
+
# Returns size of the node list (number of nodes of this branch)
|
295
|
+
#
|
296
|
+
def size
|
297
|
+
@nodelist.size
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
#
|
304
|
+
# A Node is essentially the representation of a segment in a diagram.
|
305
|
+
# It comes in two flavors: Simple nodes (SNode) and T-nodes (TNode)
|
306
|
+
# which may have "tails".
|
307
|
+
# "Node" is an abstract class - either create a SNode or a TNode.
|
308
|
+
#
|
309
|
+
class Node
|
310
|
+
private_class_method :new # Make it an abstract class
|
311
|
+
attr_accessor :name, :index
|
312
|
+
attr_reader :status, :maxrep
|
313
|
+
|
314
|
+
# +name+ :: The node's name, e.g. the segment tag
|
315
|
+
# +status+ :: A one-char string like 'M' (mandatory), 'C' (conditional)
|
316
|
+
# +rep+ :: The max. number of repetitions of this node as allowed
|
317
|
+
# by the diagram specs.
|
318
|
+
#
|
319
|
+
def Node.create(name, status, rep)
|
320
|
+
case name
|
321
|
+
when /^SG\d+$/ # T-Node
|
322
|
+
return TNode.new(name, status, rep)
|
323
|
+
else # Simple node
|
324
|
+
return SNode.new(name, status, rep)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def initialize(name, status, rep) # :nodoc:
|
329
|
+
@name, @status, @maxrep = name, status, rep
|
330
|
+
# @template = EDI::Segment.new(name, nil, nil)
|
331
|
+
end
|
332
|
+
|
333
|
+
def to_s
|
334
|
+
"%3d - %s, %s, %d" % [@index, @name, @status, @maxrep]
|
335
|
+
end
|
336
|
+
|
337
|
+
def required?
|
338
|
+
(@status =~ /[MR]/) ? true : false
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns +nil+ for an SNode or a reference to the side branch ("tail")
|
342
|
+
# of a TNode.
|
343
|
+
def tail
|
344
|
+
return nil # Only TNode implements this non-trivially
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
#
|
349
|
+
# Simple node:
|
350
|
+
# Just store name (segment tag), status (M/C etc), max repetitions
|
351
|
+
#
|
352
|
+
class SNode < Node
|
353
|
+
public_class_method :new
|
354
|
+
end
|
355
|
+
|
356
|
+
#
|
357
|
+
# T-Node:
|
358
|
+
# Additionally maintain the tail branch and the segment group name
|
359
|
+
#
|
360
|
+
class TNode < Node
|
361
|
+
public_class_method :new
|
362
|
+
attr_accessor :tail
|
363
|
+
attr_reader :sg_name
|
364
|
+
|
365
|
+
def initialize(name, status, rep)
|
366
|
+
super
|
367
|
+
@tail, @sg_name = nil, @name
|
368
|
+
end
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
####################################################################
|
373
|
+
#
|
374
|
+
# Nodes and more
|
375
|
+
|
376
|
+
NodeCoords = Struct.new( :branch, :offset, :inst_cnt )
|
377
|
+
|
378
|
+
# Node co-ordinates: A Struct consisting of the +branch+, its +offset+
|
379
|
+
# within the branch, and its instance counter +inst_cnt+ .
|
380
|
+
|
381
|
+
class NodeCoords
|
382
|
+
def to_s
|
383
|
+
"<NodeCoords>"+self.branch.object_id.to_s+', '+self.offset.to_s+', '+self.inst_cnt.to_s
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# NodeInstance
|
388
|
+
#
|
389
|
+
# A given segment of a real message instance needs an instance counter
|
390
|
+
# in addition to its location in the diagram. This applies recursively
|
391
|
+
# to all segment groups in which it is embedded. This class is equipped
|
392
|
+
# with the additional attributes ("co-ordinates") of node instances.
|
393
|
+
#
|
394
|
+
# We also use this class to determine the node instance of a segment
|
395
|
+
# when parsing a message. This is done in a sequential manner, starting
|
396
|
+
# from the current instance, by following the diagram branches up to
|
397
|
+
# the next matching segment tag/name.
|
398
|
+
#
|
399
|
+
class NodeInstance
|
400
|
+
|
401
|
+
# A new NodeInstance is inititialized to a "virtual" 0th node
|
402
|
+
# before the first real node of the diagramm referenced by +diag+ .
|
403
|
+
#
|
404
|
+
def initialize( diag )
|
405
|
+
@diag = diag # Really needed later?
|
406
|
+
|
407
|
+
# Init. to first segment of top branch, e.g. "UNH" or "EDI_DC40"
|
408
|
+
@coord = NodeCoords.new(diag.branch, 0, 0)
|
409
|
+
@coord_stack = []
|
410
|
+
@down_flag = false
|
411
|
+
end
|
412
|
+
|
413
|
+
# Returns the node's instance counter
|
414
|
+
#
|
415
|
+
def inst_cnt
|
416
|
+
@coord.inst_cnt
|
417
|
+
end
|
418
|
+
|
419
|
+
alias rep inst_cnt
|
420
|
+
|
421
|
+
|
422
|
+
# Returns diagram node corresponding to this instance's co-ordinates
|
423
|
+
#
|
424
|
+
def node
|
425
|
+
@coord.branch[@coord.offset]
|
426
|
+
end
|
427
|
+
|
428
|
+
# Delegate some getters to the underlying diagram node:
|
429
|
+
# index, maxrep, name, status
|
430
|
+
#
|
431
|
+
def name; node.name; end
|
432
|
+
def status; node.status; end
|
433
|
+
def maxrep; node.maxrep; end
|
434
|
+
def index; node.index; end
|
435
|
+
|
436
|
+
# Returns this node instance's level in the diagram.
|
437
|
+
# Note that special UN/EDIFACT rules about level 0 are acknowledged:
|
438
|
+
# level == 0 for mandatory SNode instances of the main branch with maxrep==1.
|
439
|
+
def level
|
440
|
+
depth = @coord_stack.length+1
|
441
|
+
return 0 if depth == 1 and node.maxrep == 1 and node.required? and not is_tnode? # Special Level 0 case
|
442
|
+
depth # Else: Level is depth of segment group stack + 1 (1 if no SG)
|
443
|
+
end
|
444
|
+
|
445
|
+
# Returns the branch name (segment group name)
|
446
|
+
#
|
447
|
+
def sg_name
|
448
|
+
# (node.is_a? TNode) ? node.sg_name : nil
|
449
|
+
if node.is_a? TNode
|
450
|
+
return node.sg_name
|
451
|
+
end
|
452
|
+
@coord.branch.sg_name
|
453
|
+
end
|
454
|
+
|
455
|
+
# +true+ if this is a TNode (trigger node/segment)
|
456
|
+
def is_tnode?
|
457
|
+
node.is_a? TNode
|
458
|
+
end
|
459
|
+
|
460
|
+
|
461
|
+
# Main "workhorse": Seek for next matching segment tag/name
|
462
|
+
#
|
463
|
+
# Starts at current location, follows the diagram downstream
|
464
|
+
# while searching for next matching segment.
|
465
|
+
#
|
466
|
+
# Returns updated location (self) when found, nil otherwise.
|
467
|
+
#
|
468
|
+
# Notes:
|
469
|
+
# 1. Search fails when trying to skip a required node
|
470
|
+
# 2. Search fails if we hit the end of the diagram before a match
|
471
|
+
# 3. We might need to loop through segment groups repeatedly!
|
472
|
+
#
|
473
|
+
def seek!(seg) # Segment, Regexp or String expected
|
474
|
+
name = (seg.is_a? EDI::Segment) ? seg.name : seg
|
475
|
+
# name = (seg.is_a? String) ? seg : seg.name
|
476
|
+
begin
|
477
|
+
node = self.node
|
478
|
+
# print "Looking for #{name} in #{self.name} @ level #{self.level}..."
|
479
|
+
#
|
480
|
+
# Case "match"
|
481
|
+
#
|
482
|
+
if name === node.name # == name
|
483
|
+
# puts "match!"
|
484
|
+
@coord.inst_cnt += 1
|
485
|
+
msg = "Segment #{name} at #{@coord.to_s}: More than #{node.maxrep}!"
|
486
|
+
if @coord.inst_cnt > node.maxrep
|
487
|
+
raise EDI::EDILookupError, msg
|
488
|
+
else
|
489
|
+
@down_flag = true if node.is_a? TNode
|
490
|
+
return self# .node
|
491
|
+
end
|
492
|
+
end
|
493
|
+
#
|
494
|
+
# Missed a required node?
|
495
|
+
#
|
496
|
+
if node.required? and @coord.inst_cnt == 0 # @unmatched
|
497
|
+
msg = "Missing required segment #{node.name} at #{@coord.to_s}\n" + \
|
498
|
+
" while looking for segment #{name}!"
|
499
|
+
raise EDI::EDILookupError, msg
|
500
|
+
end
|
501
|
+
# puts
|
502
|
+
end while self.next!
|
503
|
+
# Already at top level - Error condition!
|
504
|
+
raise "End of diagram exceeded!"
|
505
|
+
end
|
506
|
+
|
507
|
+
# Navigation methods, for internal use only
|
508
|
+
|
509
|
+
protected
|
510
|
+
|
511
|
+
#
|
512
|
+
# Move "up" to the TNode of this branch.
|
513
|
+
# Returns +self+, or +nil+ if already at the top.
|
514
|
+
def up!
|
515
|
+
return nil if @coord_stack.empty?
|
516
|
+
@coord = @coord_stack.pop
|
517
|
+
self
|
518
|
+
end
|
519
|
+
|
520
|
+
#
|
521
|
+
# Move to the right of the current node in this branch.
|
522
|
+
# Returns +self+, or +nil+ if already at the branch end.
|
523
|
+
def right!
|
524
|
+
return nil if @coord.offset+1 == @coord.branch.size
|
525
|
+
@coord.offset += 1
|
526
|
+
@coord.inst_cnt = 0
|
527
|
+
self
|
528
|
+
end
|
529
|
+
|
530
|
+
#
|
531
|
+
# Move to the first node of the tail branch of this TNode.
|
532
|
+
# Returns +self+, or +nil+ if there is no tail node.
|
533
|
+
def down!
|
534
|
+
this_node = self.node
|
535
|
+
return nil if (tail=this_node.tail).nil?
|
536
|
+
# Save current co-ordinates on stack
|
537
|
+
@coord_stack.push( @coord )
|
538
|
+
# Init. co-ordinates for the new level:
|
539
|
+
@coord = NodeCoords.new(tail, 0, 0)
|
540
|
+
self
|
541
|
+
end
|
542
|
+
|
543
|
+
#
|
544
|
+
# Next: Move down if TNode and same as last match (another SG instance),
|
545
|
+
# else right. Move up if neither possible.
|
546
|
+
# Returns +self+, or +nil+ if end of diag encountered.
|
547
|
+
def next!
|
548
|
+
loop do
|
549
|
+
node = self.node; r = nil
|
550
|
+
if node.is_a? TNode and @down_flag
|
551
|
+
@down_flag = false
|
552
|
+
r = self.down!
|
553
|
+
end
|
554
|
+
break if r
|
555
|
+
# Down not applicable or available; now try "right!"
|
556
|
+
break if r = self.right!
|
557
|
+
# At end of this branch - try to move up:
|
558
|
+
break if r = self.up!
|
559
|
+
# Already at top level!
|
560
|
+
return nil
|
561
|
+
end
|
562
|
+
self
|
563
|
+
end
|
564
|
+
|
565
|
+
end
|
566
|
+
|
567
|
+
end # module EDI::Diagram
|