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