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
@@ -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
|