edi4r 0.9.4.1 → 0.9.6.2

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/AuthorCopyright +3 -3
  3. data/{ChangeLog → Changelog} +60 -0
  4. data/README +15 -10
  5. data/Tutorial +2 -3
  6. data/VERSION +1 -1
  7. data/bin/edi2xml.rb +12 -16
  8. data/bin/editool.rb +9 -5
  9. data/bin/sedas2eancom02.rb +1385 -0
  10. data/bin/xml2edi.rb +7 -12
  11. data/data/edifact/iso9735/SDCD.20000.csv +1 -0
  12. data/data/edifact/iso9735/SDCD.3for2.csv +1 -0
  13. data/data/edifact/iso9735/SDED.20000.csv +6 -0
  14. data/data/edifact/iso9735/SDED.30000.csv +43 -43
  15. data/data/edifact/iso9735/SDED.3for2.csv +6 -0
  16. data/data/edifact/iso9735/SDED.40000.csv +129 -129
  17. data/data/edifact/iso9735/SDED.40100.csv +130 -130
  18. data/data/edifact/iso9735/SDMD.20000.csv +6 -0
  19. data/data/edifact/iso9735/SDMD.30000.csv +6 -6
  20. data/data/edifact/iso9735/SDMD.3for2.csv +6 -0
  21. data/data/edifact/iso9735/SDMD.40000.csv +17 -17
  22. data/data/edifact/iso9735/SDMD.40100.csv +17 -17
  23. data/data/edifact/iso9735/SDSD.20000.csv +5 -0
  24. data/data/edifact/iso9735/SDSD.3for2.csv +5 -0
  25. data/data/edifact/untdid/EDMD.d01b.csv +1 -1
  26. data/data/sedas/EDCD..csv +0 -0
  27. data/data/sedas/EDED..csv +859 -0
  28. data/data/sedas/EDMD..csv +16 -0
  29. data/data/sedas/EDSD..csv +44 -0
  30. data/lib/edi4r.rb +147 -67
  31. data/lib/edi4r/ansi_x12-rexml.rb +91 -0
  32. data/lib/edi4r/ansi_x12.rb +1684 -0
  33. data/lib/edi4r/diagrams.rb +75 -14
  34. data/lib/edi4r/edifact-rexml.rb +4 -3
  35. data/lib/edi4r/edifact.rb +505 -202
  36. data/lib/edi4r/rexml.rb +13 -7
  37. data/lib/edi4r/sedas.rb +854 -0
  38. data/lib/edi4r/standards.rb +150 -33
  39. data/test/damaged_file.edi +1 -0
  40. data/test/eancom2webedi.rb +1 -0
  41. data/test/groups.edi +1 -1
  42. data/test/test_basics.rb +16 -9
  43. data/test/test_edi_split.rb +30 -0
  44. data/test/test_loopback.rb +7 -2
  45. data/test/test_rexml.rb +34 -2
  46. data/test/test_service_messages.rb +190 -0
  47. data/test/test_streaming.rb +167 -0
  48. data/test/test_tut_examples.rb +3 -1
  49. data/test/webedi2eancom.rb +1 -0
  50. metadata +121 -77
@@ -1,10 +1,11 @@
1
+ # -*- encoding: iso-8859-1 -*-
1
2
  # Classes related to message diagrams and validation
2
3
  # for the EDI module "edi4r", a class library
3
4
  # to parse and create UN/EDIFACT and other EDI data
4
5
  #
5
6
  # :include: ../../AuthorCopyright
6
7
  #
7
- # $Id: diagrams.rb,v 1.8 2006/05/26 16:57:37 werntges Exp $
8
+ # $Id: diagrams.rb,v 1.8 2006/05/26 16:57:37 werntges Exp werntges $
8
9
  #--
9
10
  # $Log: diagrams.rb,v $
10
11
  # Revision 1.8 2006/05/26 16:57:37 werntges
@@ -97,18 +98,25 @@ module EDI::Diagram
97
98
  # std:: The syntax standard key. Currently supported:
98
99
  # - 'E' (EDIFACT),
99
100
  # - 'I' (SAP IDOC)
101
+ # - 'S' (SEDAS, experimental)
102
+ # - 'A' (ANSI X.12, limited)
100
103
  # params:: A hash of parameters that uniquely identify the selected diagram.
101
104
  # Internal use only - see source code for details.
102
105
  #
103
106
  def Diagram.create( std, params )
104
107
  case std
105
108
  when 'E' # UN/EDIFACT
106
- par = {:d0051 => 'UN',
107
- # :d0057 => '',
109
+ par = {
110
+ :d0051 => 'UN',
111
+ :d0057 => nil,
108
112
  :is_iedi => false }.update( params )
109
113
  when 'I' # SAP IDocs
110
114
  par = params
111
115
  # raise "Not implemented yet!"
116
+ when 'S' # SEDAS
117
+ par = params
118
+ when 'A' # ANSI X12
119
+ par = params
112
120
  else
113
121
  raise "Unsupported syntax standard: #{std}"
114
122
  end
@@ -120,7 +128,7 @@ module EDI::Diagram
120
128
  key = par.sort {|a,b| a.to_s <=> b.to_s}.hash
121
129
  obj = @@cache[key]
122
130
  return obj unless obj.nil?
123
-
131
+
124
132
  obj = new( std, par )
125
133
  @@cache[key] = obj # cache & return it
126
134
 
@@ -132,13 +140,30 @@ module EDI::Diagram
132
140
 
133
141
  def initialize( std, par ) # :nodoc:
134
142
  case std
143
+ when 'A' # ANSI X12
144
+ @base_key = [par[:ST01], # msg type, e.g. 837
145
+ par[:GS08][0,3], # version
146
+ par[:GS08][3,2], # release,
147
+ par[:GS08][5,1], # sub-version
148
+ '',
149
+ # par[:GS08][6..-1], # assoc. assigned code (subset)
150
+ ''].join(':')
151
+ @msg_type = par[:ST01]
152
+
153
+ @dir = EDI::Dir::Directory.create(std, par )
154
+ # :d0065 => @msg_type,
155
+ # :d0052 => par[:d0052],
156
+ # :d0054 => par[:d0054],
157
+ # :d0051 => par[:d0051],
158
+ # :d0057 => par[:d0057],
159
+ # :is_iedi => par[:is_iedi])
135
160
  when 'E' # UN/EDIFACT
136
161
  @base_key = [par[:d0065], # msg type
137
162
  par[:d0052], # version
138
163
  par[:d0054], # release,
139
164
  par[:d0051], # resp. agency
140
- '',
141
- # par[:d0057], # assoc. assigned code (subset)
165
+ # '',
166
+ par[:d0057], # assoc. assigned code (subset)
142
167
  ''].join(':')
143
168
  @msg_type = par[:d0065]
144
169
 
@@ -160,10 +185,23 @@ module EDI::Diagram
160
185
  @msg_type = par[:IDOCTYPE]
161
186
 
162
187
  @dir = EDI::Dir::Directory.create(std,
163
- :DOCTYPE => @msg_type,
188
+ :IDOCTYPE => @msg_type,
164
189
  :EXTENSION => par[:EXTENSION],
165
190
  :SAPTYPE => par[:SAPTYPE])
166
191
  # raise "Not implemented yet!"
192
+
193
+ when 'S' # SEDAS
194
+ @base_key = [par[:SEDASTYPE],
195
+ '',
196
+ '',
197
+ '',
198
+ '',
199
+ # par[:d0057], # assoc. assigned code (subset)
200
+ ''].join(':')
201
+ @msg_type = par[:SEDASTYPE]
202
+
203
+ @dir = EDI::Dir::Directory.create(std)
204
+
167
205
  else
168
206
  raise "Unsupported syntax standard: #{std}"
169
207
  end
@@ -219,20 +257,31 @@ module EDI::Diagram
219
257
  attr_reader :sg_name
220
258
 
221
259
  # 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).
260
+ # that selects the directory entry and its +sg_name+ (if not top branch).
223
261
  # +root+ is a reference to the Diagram it belongs to.
224
262
 
225
263
  def initialize(key, sg_name, root)
226
- # puts "Creating branch for key `#{key+sg_name}'..."
264
+ # warn "Creating branch for key `#{key||''+sg_name||''}'..."
227
265
  @key = key
228
266
  @sg_name = sg_name
229
267
  @root = root
230
268
 
231
269
  @nodelist=[]
232
270
  b = @root.dir.message( key+sg_name.to_s )
233
- raise "Lookup failed for key `#{key+sg_name.to_s}'" unless b
271
+ =begin
272
+ # UN/EDIFACT subsets only:
273
+ if b.nil? && key =~ /(\w{6}:\w+:\w+:\w+:)(.+?)(:.*)/
274
+ puts "2: #{key}"
275
+ @key = key = $1+$3 # Discard the subset DE
276
+ puts "3: #{key}"
277
+ EDI::logger.warn "Subset #{$2} data not found - trying standard instead..."
278
+ b = @root.dir.message( key+sg_name.to_s )
279
+ end
280
+ =end
281
+ raise EDI::EDILookupError, "Lookup failed for key `#{key+sg_name.to_s}' - known names: #{@root.dir.message_names.join(', ')}" unless b
234
282
  @desc = b.desc
235
283
  b.each {|obj| @nodelist << Node.create( obj.name, obj.status, obj.maxrep )}
284
+ raise "Empty branch! key, sg = #{key}, #{sg_name}" if @nodelist.empty?
236
285
  end
237
286
 
238
287
  #
@@ -297,6 +346,13 @@ module EDI::Diagram
297
346
  @nodelist.size
298
347
  end
299
348
 
349
+ # Returns TRUE if branch is empty. Example:
350
+ # The tail of a segment group that consists of just the trigger segment
351
+ #
352
+ def empty?
353
+ @nodelist.size==0
354
+ end
355
+
300
356
  end
301
357
 
302
358
 
@@ -327,7 +383,8 @@ module EDI::Diagram
327
383
 
328
384
  def initialize(name, status, rep) # :nodoc:
329
385
  @name, @status, @maxrep = name, status, rep
330
- # @template = EDI::Segment.new(name, nil, nil)
386
+ # @template = EDI::Segment.new(name, nil, nil)
387
+ # warn "Creating node: #{self.to_s}"
331
388
  end
332
389
 
333
390
  def to_s
@@ -430,7 +487,7 @@ module EDI::Diagram
430
487
  #
431
488
  def name; node.name; end
432
489
  def status; node.status; end
433
- def maxrep; node.maxrep; end
490
+ def maxrep; node.maxrep; end
434
491
  def index; node.index; end
435
492
 
436
493
  # Returns this node instance's level in the diagram.
@@ -475,10 +532,14 @@ module EDI::Diagram
475
532
  # name = (seg.is_a? String) ? seg : seg.name
476
533
  begin
477
534
  node = self.node
478
- # print "Looking for #{name} in #{self.name} @ level #{self.level}..."
535
+ # warn "Looking for #{name} in #{self.name} @ level #{self.level} while node.maxrep=#{node.maxrep}..."
479
536
  #
480
537
  # Case "match"
481
538
  #
539
+ if node.nil?
540
+ warn "#{name}: #{@coord.offset} #{@coord.branch.sg_name} #{@coord.branch.desc} #{@coord.branch.size}"
541
+ raise "#{self}: no node!"
542
+ end
482
543
  if name === node.name # == name
483
544
  # puts "match!"
484
545
  @coord.inst_cnt += 1
@@ -532,7 +593,7 @@ module EDI::Diagram
532
593
  # Returns +self+, or +nil+ if there is no tail node.
533
594
  def down!
534
595
  this_node = self.node
535
- return nil if (tail=this_node.tail).nil?
596
+ return nil if (tail=this_node.tail).nil? or tail.empty?
536
597
  # Save current co-ordinates on stack
537
598
  @coord_stack.push( @coord )
538
599
  # Init. co-ordinates for the new level:
@@ -1,3 +1,4 @@
1
+ # -*- encoding: iso-8859-1 -*-
1
2
  # UN/EDIFACT add-ons to EDI module,
2
3
  # Methods for XML support for the UN/EDIFACT module
3
4
  #
@@ -36,12 +37,12 @@ module EDI::E
36
37
  _header = _root.elements["Header"]
37
38
  _trailer = _root.elements["Trailer"]
38
39
  _una = _header.elements["Parameter[@name='UNA']"]
39
- _una = _una.text if _una
40
+ _una = _una.text.strip if _una
40
41
  raise "Empty UNA" if _una and _una.empty? # remove later!
41
42
  # S001: Works for both batch and interactive EDI:
42
43
  _s001 = _header.elements["Segment/CDE[@name='S001']"]
43
44
  _version = _s001.elements["DE[@name='0002']"].text.to_i
44
- _charset = _s001.elements["DE[@name='0001']"].text
45
+ _charset = _s001.elements["DE[@name='0001']"].text.strip
45
46
  params = { :charset => _charset, :version => _version }
46
47
  if _una
47
48
  params[:una_string] = _una
@@ -83,7 +84,7 @@ module EDI::E
83
84
  # S001: Works for both batch and interactive EDI:
84
85
  _s001 = _header.elements["Segment/CDE[@name='S001']"]
85
86
  _version = _s001.elements["DE[@name='0002']"].text.to_i
86
- _charset = _s001.elements["DE[@name='0001']"].text
87
+ _charset = _s001.elements["DE[@name='0001']"].text.strip
87
88
  params = { :charset => _charset, :version => _version }
88
89
  if _una
89
90
  params[:una_string] = _una
@@ -1,46 +1,9 @@
1
+ # -*- encoding: iso-8859-1 -*-
1
2
  # UN/EDIFACT add-ons to EDI module,
2
3
  # API to parse and create UN/EDIFACT data
3
4
  #
4
5
  # :include: ../../AuthorCopyright
5
6
  #
6
- # $Id: edifact.rb,v 1.10 2006/08/01 11:14:07 werntges Exp $
7
- #--
8
- # $Log: edifact.rb,v $
9
- # Revision 1.10 2006/08/01 11:14:07 werntges
10
- # Release 0.9.4.1 -- see ChangeLog
11
- #
12
- # Revision 1.9 2006/05/26 16:56:41 werntges
13
- # V 0.9.3 snapshot. Many improvements (see ChangeLog), RDoc, more I-EDI support
14
- #
15
- # Revision 1.8 2006/05/01 22:23:55 werntges
16
- # Preparing for 0.9.2: See ChangeLog for new features
17
- #
18
- # Revision 1.7 2006/04/28 14:31:50 werntges
19
- # 0.9.1 snapshot
20
- #
21
- # Revision 1.6 2006/03/28 22:23:40 werntges
22
- # changed to using symbols as parameter keys, e.g. :charset
23
- # implemented as new module EDI::E, abandoning Interchange_E and alike
24
- # bug fixes re. UNA (@una, setters)
25
- #
26
- # Revision 1.5 2006/03/22 16:52:42 werntges
27
- # snapshot after edi4r-0.8.2.gem
28
- #
29
- # Revision 1.4 2004/02/19 17:31:52 heinz
30
- # HWW: Snapshot after REMADV mapping
31
- #
32
- # Revision 1.3 2004/02/14 12:10:19 heinz
33
- # HWW: Minor improvements
34
- #
35
- # Revision 1.2 2004/02/11 23:31:59 heinz
36
- # HWW: First release after finishing basic tests
37
- #
38
- # Revision 1.1 2004/02/10 00:25:13 heinz
39
- # Initial revision
40
- #
41
- #
42
- # Derived from "edi.rb" V 1.11 on 2004-02-09 by HWW
43
- #
44
7
  # To-do list:
45
8
  # validate - add functionality
46
9
  # charset - check for valid chars (add UNOD-UNOZ)
@@ -81,6 +44,11 @@ module EDI::E
81
44
  # CDE = "1234::567" --> ['1234','','567']
82
45
  # CDE = ":::SOMETEXT" --> ['','','','SOMETEXT']
83
46
  # Seg = "TAG+1++2:3:4+A?+B=C" --> ['TAG','1','','2:3:4','A+B=C']
47
+ # Seg = "FTX+PUR+1++P:aP?: 120,00 ??:nP?: 100,10??"
48
+ # --> ['FTX','PUR','1','','P:aP?: 120,00 ??:nP?: 100,10??']
49
+ # ** OR ** --> ['FTX','PUR','1','','P:aP?: 120,00 ??:nP?: 100,10?']
50
+ # CDE = "P:aP?: 120,00 ??:nP?: 100,10??"
51
+ # --> ['P','aP: 120,00 ?','nP: 100,10?']
84
52
  #
85
53
  # NOTE: This function might be a good candidate for implementation in "C"
86
54
  #
@@ -102,12 +70,12 @@ module EDI::E
102
70
  item += str[start...match_at]
103
71
  # Count escapes in front of separator. No real separator if odd!
104
72
  escapes = count_escapes( item, e )
105
- if escapes & 1 == 1 # odd
73
+ if escapes.odd?
106
74
  raise EDISyntaxError, "Pending escape char in #{str}" if match_at == str.length
107
- (escapes/2+1).times {item.chop!} # chop off duplicate escapes
75
+ # (escapes/2+1).times {item.chop!} # chop off duplicate escapes
108
76
  item << s # add separator as regular character
109
77
  else # even
110
- (escapes/2).times {item.chop!} # chop off duplicate escapes
78
+ # (escapes/2).times {item.chop!} # chop off duplicate escapes
111
79
  results << item
112
80
  item = ''
113
81
  end
@@ -152,8 +120,9 @@ module EDI::E
152
120
  #
153
121
  # Currently supported formats: 101, 102, 201, 203, 204
154
122
 
155
- class ::Time
156
- attr_accessor :format
123
+ class EDI::Time
124
+
125
+ @@to_s_callbacks << :to_s_edifact
157
126
 
158
127
  def Time.edifact(str, fmt=102)
159
128
  msg = "Time.edifact: #{str} does not match format #{fmt}"
@@ -163,29 +132,29 @@ module EDI::E
163
132
  raise msg unless rc and rc==0; warn msg if $4
164
133
  year = $1.to_i
165
134
  year += (year < 69) ? 2000 : 1900 # See ParseDate
166
- dtm = Time.local(year, $2, $3)
135
+ dtm = EDI::Time.local(year, $2, $3)
167
136
 
168
137
  when '102'
169
138
  rc = str =~ /(\d\d\d\d)(\d\d)(\d\d)(.+)?/
170
139
  raise msg unless rc and rc==0; warn msg if $4
171
- dtm = Time.local($1, $2, $3)
140
+ dtm = EDI::Time.local($1, $2, $3)
172
141
 
173
142
  when '201'
174
143
  rc = str =~ /(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(.+)?/
175
144
  raise msg unless rc and rc==0; warn msg if $6
176
145
  year = $1.to_i
177
146
  year += (year < 69) ? 2000 : 1900 # See ParseDate
178
- dtm = Time.local(year, $2, $3, $4, $5)
147
+ dtm = EDI::Time.local(year, $2, $3, $4, $5)
179
148
 
180
149
  when '203'
181
150
  rc = str =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(.+)?/
182
151
  raise msg unless rc and rc==0; warn msg if $6
183
- dtm = Time.local($1, $2, $3, $4, $5)
152
+ dtm = EDI::Time.local($1, $2, $3, $4, $5)
184
153
 
185
154
  when '204'
186
155
  rc = str =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(.+)?/
187
156
  raise msg unless rc and rc==0; warn msg if $7
188
- dtm = Time.local($1, $2, $3, $4, $5, $6)
157
+ dtm = EDI::Time.local($1, $2, $3, $4, $5, $6)
189
158
 
190
159
  else
191
160
  raise "Time.edifact: Format #{fmt} not supported - sorry"
@@ -194,10 +163,7 @@ module EDI::E
194
163
  dtm
195
164
  end
196
165
 
197
- alias to_s_orig to_s
198
-
199
- def to_s
200
- return to_s_orig unless @format
166
+ def to_s_edifact
201
167
  case @format.to_s
202
168
  when '101'
203
169
  "%02d%02d%02d" % [year % 100, mon, day]
@@ -209,9 +175,8 @@ module EDI::E
209
175
  "%04d%02d%02d%02d%02d" % [year, mon, day, hour, min]
210
176
  when '204'
211
177
  "%04d%02d%02d%02d%02d%2d" % [year, mon, day, hour, min, sec]
212
- else # Should never occur
213
- raise "Time.edifact: Format #{format
214
- } not supported - sorry"
178
+ else
179
+ nil # nil indicates that there was no matching format
215
180
  end
216
181
  end
217
182
  end
@@ -341,8 +306,10 @@ module EDI::E
341
306
  special_chars.push @rep_sep if root.version == 4
342
307
  special_chars = special_chars.map{|c| c.chr}
343
308
  @pattern_esc = Regexp.new( [ '([', special_chars, '])' ].flatten.join)
309
+ esc_str = @esc_char.chr
310
+ esc_str << '\\' if esc_str == '\\' # Must escape '\' in a regex
344
311
  @pattern_unesc = Regexp.new( [
345
- '([^', @esc_char, ']?)', '[', @esc_char,
312
+ '([^', esc_str, ']?)', '[', esc_str,
346
313
  ']([', special_chars,'])'
347
314
  ].flatten.join )
348
315
  root.show_una = true
@@ -397,11 +364,11 @@ module EDI::E
397
364
  #
398
365
  # === Notes
399
366
  # * Date and time in S004 are set to the current values automatically.
400
- # * Add or change any data element later. except those in S001.
367
+ # * Add or change any data element later, except those in S001.
401
368
  #
402
369
  # === Examples:
403
370
  # - ic = EDI::E::Interchange.new # Empty interchange, default settings
404
- # - ic = EDI::E::Interchange.new(:charset=>'UNOC',:output_mode=>:linebreak)
371
+ # - ic = EDI::E::Interchange.new(:charset=>'UNOC', :output_mode=>:linebreak)
405
372
 
406
373
  def initialize( user_par={} )
407
374
  super( user_par ) # just in case...
@@ -437,7 +404,7 @@ module EDI::E
437
404
  @header.cS001.d0002 = par[:version]
438
405
 
439
406
  @header.cS002.d0004 = par[:sender] unless par[:sender].nil?
440
- @header.cS003.d0010 = par[:recipient] unless par[:recip].nil?
407
+ @header.cS003.d0010 = par[:recipient] unless par[:recipient].nil?
441
408
  @header.cS302.d0300 = par[:interchange_control_reference]
442
409
  # FIXME: More to do in S302...
443
410
 
@@ -450,6 +417,7 @@ module EDI::E
450
417
  @trailer.d0036 = 0
451
418
  ch, ct = @header.cS302, @trailer.cS302
452
419
  ct.d0300, ct.d0303, ct.d0051, ct.d0304 = ch.d0300, ch.d0303, ch.d0051, ch.d0304
420
+
453
421
  else # Batch EDI
454
422
 
455
423
  @header = new_segment('UNB')
@@ -457,7 +425,7 @@ module EDI::E
457
425
  @header.cS001.d0001 = par[:charset]
458
426
  @header.cS001.d0002 = par[:version]
459
427
  @header.cS002.d0004 = par[:sender] unless par[:sender].nil?
460
- @header.cS003.d0010 = par[:recipient] unless par[:recip].nil?
428
+ @header.cS003.d0010 = par[:recipient] unless par[:recipient].nil?
461
429
  @header.d0020 = par[:interchange_control_reference]
462
430
 
463
431
  x= :application_reference; @header.d0026 = par[x] unless par[x].nil?
@@ -473,108 +441,14 @@ module EDI::E
473
441
  end
474
442
  end
475
443
 
476
-
477
444
  #
478
445
  # Reads EDIFACT data from given stream (default: $stdin),
479
446
  # parses it and returns an Interchange object
480
447
  #
481
448
  def Interchange.parse( hnd=$stdin, auto_validate=true )
482
- ic = nil
483
- buf = hnd.read
484
- return ic if buf.empty?
485
-
486
- ic, segment_list = Interchange.parse_buffer( buf )
487
- # Remember to update ndb to SV4-1 now if d0076 of UNB/S001 tells so
488
-
489
- # Deal with 'trash' after UNZ
490
-
491
- if ic.is_iedi?
492
- init_seg = Regexp.new('^UIB'); tag_init = 'UIB'
493
- exit_seg = Regexp.new('^UIZ'); tag_exit = 'UIZ'
494
- else
495
- init_seg = Regexp.new('^UNB'); tag_init = 'UNB'
496
- exit_seg = Regexp.new('^UNZ'); tag_exit = 'UNZ'
497
- end
498
-
499
- last_seg = nil
500
- loop do
501
- last_seg = segment_list.pop
502
- case last_seg
503
- when /^[A-Z]{3}/ # Segment tag?
504
- unless last_seg =~ exit_seg
505
- raise "Parse error: #{tag_exit} is not last segment! Found: #{last_seg}"
506
- end
507
- break
508
- when /\n/, /\r\n/, ''
509
- # ignore linebreaks at end of file, do not warn.
510
- else
511
- warn "WARNING: Data found after #{tag_exit} segment - ignored!"
512
- warn "Found: \'#{last_seg}\'"
513
- end
514
- end
515
- trailer = Segment.parse(ic, last_seg, tag_exit)
516
-
517
- # Assure that there is only one UNB/UNZ or UIB/UIZ
518
-
519
- err_flag = false
520
- segment_list.each do |seg|
521
- if seg =~ init_seg
522
- warn "ERROR: Another interchange header found in file!"
523
- err_flag = true
524
- end
525
- if seg =~ exit_seg
526
- warn "ERROR: Another interchange trailer found in file!"
527
- err_flag = true
528
- end
529
- end
530
- raise "FATAL ERROR - exiting" if err_flag
531
-
532
- # OK, ready to deal with content now:
533
-
534
- case segment_list[0]
535
- when /^UNH/
536
- init_seg = Regexp.new('^UNH')
537
- exit_seg = Regexp.new('^UNT')
538
- group_mode = false
539
- when /^UNG/
540
- init_seg = Regexp.new('^UNG')
541
- exit_seg = Regexp.new('^UNE')
542
- group_mode = true
543
- when /^UIH/ # There is no 'UIG'!
544
- init_seg = Regexp.new('^UIH')
545
- exit_seg = Regexp.new('^UIT')
546
- group_mode = false
547
- else
548
- raise "Expected: UNH, UNG, or UIH. Found: #{segment_list[0]}"
549
- end
550
-
551
- while segbuf = segment_list.shift
552
- case segbuf
553
-
554
- when init_seg
555
- sub_list = Array.new
556
- sub_list.push segbuf
557
-
558
- when exit_seg
559
- sub_list.push segbuf
560
- if group_mode
561
- ic.add( MsgGroup.parse(ic, sub_list), auto_validate )
562
- else
563
- ic.add( Message.parse(ic, sub_list), auto_validate )
564
- end
565
-
566
- else
567
- sub_list.push segbuf
568
- end
569
-
570
- end # while
571
-
572
- # Finally add the trailer from the originally read data,
573
- # thereby overwriting the temporary interchange trailer.
574
- # Note that the temporary trailer got modified by add()ing
575
- # to the interchange.
576
- ic.trailer = trailer
577
- ic
449
+ builder = StreamingBuilder.new( auto_validate )
450
+ builder.go( hnd )
451
+ builder.interchange
578
452
  end
579
453
 
580
454
  #
@@ -586,47 +460,24 @@ module EDI::E
586
460
  # Intended use:
587
461
  # Efficient routing by reading just UNB data: sender/recipient/ref/test
588
462
  #
589
- def Interchange.peek(hnd=$stdin, maxlen=128) # Handle to input stream
590
- buf = hnd.read( maxlen )
591
- return nil if buf.empty?
592
- ic, dummy = Interchange.parse_buffer( buf, 1 )
593
-
594
- # Create a dummy trailer
595
- tag = ic.is_iedi? ? 'UIZ' : 'UNZ'
596
- trailer_string = tag.dup << ic.una.de_sep << '0' << ic.una.de_sep << '0'
597
- ic.trailer= Segment.parse(ic, trailer_string, tag)
598
-
599
- ic
600
- end
601
-
602
- #
603
- # INTERNAL USE ONLY:
604
- # Turn buffer into array of segments (array size <= s_max),
605
- # read UNB/UIB, create an Interchange object with a header,
606
- # return this interchange and the array of segments
607
- #
608
- def Interchange.parse_buffer( buf, s_max=0 ) # :nodoc:
609
- case buf
610
- # UN/EDIFACT case
611
- when /^(UNA......)?\r?\n?U([IN])B.(UNO[A-Z]).([1-4])/
612
- par = @@interchange_defaults.dup
613
- par[:una_string], par[:charset], par[:version], par[:i_edi] =
614
- $1, $3, $4.to_i, $2=='I'
615
- ic = Interchange.new( par )
616
- buf.sub!(/^UNA....../,'') # remove pseudo segment
617
-
463
+ def Interchange.peek(hnd=$stdin, params={}) # Handle to input stream
464
+ builder = StreamingBuilder.new( false )
465
+ if params[:deep_peek]
466
+ def builder.on_segment( s, tag )
467
+ end
618
468
  else
619
- raise "Is this really UN/EDIFACT? File starts with: #{buf[0,23]}"
469
+ def builder.on_ung( s )
470
+ throw :done
471
+ end
472
+ def builder.on_unh_uih( s, tag )
473
+ throw :done # FIXME: UNZ??
474
+ end
620
475
  end
621
-
622
- segments = EDI::E.edi_split(buf, ic.una.seg_term, ic.una.esc_char, s_max)
623
- # Remove <cr><lf> (some sources are not EDIFACT compliant)
624
- segments.each {|s| s.sub!(/\s*(.*)/, '\1')}
625
- ic.header = Segment.parse(ic, segments.shift, ic.is_iedi? ? 'UIB':'UNB')
626
-
627
- [ic, segments]
476
+ builder.go( hnd )
477
+ builder.interchange
628
478
  end
629
479
 
480
+
630
481
  #
631
482
  # Returns +true+ if this is an I-EDI interchange (Interactive EDI)
632
483
  #
@@ -1009,8 +860,8 @@ module EDI::E
1009
860
  end
1010
861
 
1011
862
 
1012
- def add( msg )
1013
- super
863
+ def add( msg, auto_validate=true )
864
+ super( msg )
1014
865
  @trailer.d0060 = @trailer.d0060.to_i if @trailer.d0060.is_a? String
1015
866
  @trailer.d0060 += 1
1016
867
  end
@@ -1059,8 +910,10 @@ module EDI::E
1059
910
  }
1060
911
  @@message_default_keys = @@message_defaults.keys
1061
912
 
1062
- # Creates an empty UN/EDIFACT message
1063
- # Don't use directly - use +new_message+ of class Interchange or MsgGroup instead!
913
+ # Creates an empty UN/EDIFACT message.
914
+ #
915
+ # Don't use directly - call method +new_message+ of class Interchange
916
+ # or MsgGroup instead!
1064
917
  #
1065
918
  # == First parameter
1066
919
  #
@@ -1109,6 +962,7 @@ module EDI::E
1109
962
  :d0065 => @name, :d0052=> @version, :d0054=> @release,
1110
963
  :d0051 => @resp_agency, :d0057 => @subset, :is_iedi => root.is_iedi?
1111
964
  }
965
+ par[:d0002] = self.root.header.cS001.d0002 if %w/CONTRL AUTACK KEYMAN/.include? @name # TODO: Experimental - revise!
1112
966
  @maindata = EDI::Dir::Directory.create(root.syntax, par )
1113
967
 
1114
968
  if root.is_iedi?
@@ -1171,7 +1025,7 @@ module EDI::E
1171
1025
 
1172
1026
  # Internal use only!
1173
1027
 
1174
- def parse_segment(buf, tag) # :nodoc:
1028
+ def parse_segment(buf, tag=nil) # :nodoc:
1175
1029
  Segment.parse(self, buf, tag)
1176
1030
  end
1177
1031
 
@@ -1278,7 +1132,7 @@ module EDI::E
1278
1132
 
1279
1133
  ni.seek!( @header )
1280
1134
  @header.update_with( ni )
1281
- each do |seg|
1135
+ each do |seg|
1282
1136
  if ni.seek!(seg)
1283
1137
  seg.update_with( ni )
1284
1138
  else
@@ -1375,7 +1229,7 @@ module EDI::E
1375
1229
 
1376
1230
  # FIXME: Code redundancy in type detection - remove later!
1377
1231
  case id
1378
- when /[CES]\d{3}/ # Composite
1232
+ when /[CES]\d{3}/ # Composite
1379
1233
  add new_CDE(id, status)
1380
1234
  when /\d{4}/ # Simple DE
1381
1235
  add new_DE(id, status, fmt_of_DE(id))
@@ -1400,6 +1254,7 @@ module EDI::E
1400
1254
 
1401
1255
  def Segment.parse (p, buf, tag_expected=nil)
1402
1256
  # Buffer contains a single segment
1257
+
1403
1258
  obj_list = EDI::E::edi_split( buf, p.root.una.de_sep, p.root.una.esc_char )
1404
1259
  tag = obj_list.shift # First entry must be the segment tag
1405
1260
 
@@ -1624,4 +1479,452 @@ module EDI::E
1624
1479
  end
1625
1480
  end
1626
1481
 
1627
- end # module EDI
1482
+
1483
+ #########################################################################
1484
+ #
1485
+ # = Class StreamingParser
1486
+ #
1487
+ # == Introduction
1488
+ #
1489
+ # Turning a whole EDI interchange into an EDI::E::Interchange object
1490
+ # with method +parse+ is both convenient and memory consuming.
1491
+ # Sometimes, interchanges become just too large to keep them completely
1492
+ # in memory.
1493
+ # The same reasoning holds for large XML documents, where there is a
1494
+ # common solution: The SAX/SAX2 API, a streaming approach. This class
1495
+ # implements the same idea for UN/EDIFACT data.
1496
+ #
1497
+ # Use StreamingParser instances to parse UN/EDIFACT data *sequentially*.
1498
+ # Sequential parsing saves main memory and is applicable to
1499
+ # arbitrarily large interchanges.
1500
+ #
1501
+ # At its core lies method +go+. It scans the input stream and
1502
+ # employs callbacks <tt>on_*</tt> which implement most of the parser tasks.
1503
+ #
1504
+ # == Syntax check
1505
+ #
1506
+ # Without your customizing the callbacks, this parser just scans
1507
+ # through the data. Only callback <tt>on_error()</tt> contains code:
1508
+ # It raises an exception telling you about the location and kind
1509
+ # of syntax error encountered.
1510
+ #
1511
+ # === Example: Syntax check
1512
+ #
1513
+ # parser = EDI::E::StreamingParser.new
1514
+ # parser.go( File.open 'damaged_file.edi' )
1515
+ # --> EDI::EDISyntaxError at offset 1234, last chars = UNt+1+0
1516
+ #
1517
+ #
1518
+ # == Callbacks
1519
+ #
1520
+ # Most callbacks provided here are just empty shells. They usually receive
1521
+ # a string of interest (a segment content, i.e. everything from the segment
1522
+ # tag to and excluding the segment terminator) and also the
1523
+ # segment tag as a separate string when tags could differ.
1524
+ #
1525
+ # Overwrite them to adapt the parser to your needs!
1526
+ #
1527
+ # === Example: Counting segments
1528
+ #
1529
+ # class MyParser < EDI::E::StreamingParser
1530
+ # attr_reader :counters
1531
+ #
1532
+ # def initialize
1533
+ # @counters = Hash.new(0)
1534
+ # super
1535
+ # end
1536
+ #
1537
+ # def on_segment( s, tag )
1538
+ # @counters[tag] += 1
1539
+ # end
1540
+ # end
1541
+ #
1542
+ # parser = MyParser.new
1543
+ # parser.go( File.open 'myfile.edi' )
1544
+ # puts "Segment tag statistics:"
1545
+ # parser.counters.keys.sort.each do |tag|
1546
+ # print "%03s: %4d\n" % [ tag, parser.counters[tag] ]
1547
+ # end
1548
+ #
1549
+ # == Want to save time? Throw <tt>:done</tt> when already done!
1550
+ #
1551
+ # Most callbacks may <b>terminate further parsing</b> by throwing
1552
+ # symbol <tt>:done</tt>. This saves a lot of time e.g. if you already
1553
+ # found what you were looking for. Otherwise, parsing continues
1554
+ # until +getc+ hits +EOF+ or an error occurs.
1555
+ #
1556
+ # === Example: A simple search
1557
+ #
1558
+ # parser = EDI::E::StreamingParser.new
1559
+ # def parser.on_segment( s, tag ) # singleton
1560
+ # if tag == 'ADJ'
1561
+ # puts "Interchange contains at least one segment ADJ !"
1562
+ # puts "Here is its contents: #{s}"
1563
+ # throw :done # Skip further parsing
1564
+ # end
1565
+ # end
1566
+ # parser.go( File.open 'myfile.edi' )
1567
+
1568
+ class StreamingParser
1569
+
1570
+ def initialize
1571
+ @path = 'input stream'
1572
+ end
1573
+
1574
+ # Convenience method. Returns the path of the File object
1575
+ # passed to method +go+ or just string 'input stream'
1576
+ def path
1577
+ @path
1578
+ end
1579
+
1580
+ # Called at start of reading - overwrite for your init purposes.
1581
+ # Note: Must *not* throw <tt>:done</tt> !
1582
+ #
1583
+ def on_interchange_start
1584
+ end
1585
+
1586
+ # Called at EOF - overwrite for your cleanup purposes.
1587
+ # Note: Must *not* throw <tt>:done</tt> !
1588
+ #
1589
+ def on_interchange_end
1590
+ end
1591
+
1592
+ # Called when UNA pseudo segment encountered
1593
+ #
1594
+ def on_una( s )
1595
+ end
1596
+
1597
+ # Called when UNB or UIB encountered
1598
+ #
1599
+ def on_unb_uib( s, tag )
1600
+ end
1601
+
1602
+ # Called when UNZ or UIZ encountered
1603
+ #
1604
+ def on_unz_uiz( s, tag )
1605
+ end
1606
+
1607
+ # Called when UNG encountered
1608
+ #
1609
+ def on_ung( s )
1610
+ end
1611
+
1612
+ # Called when UNE encountered
1613
+ #
1614
+ def on_une( s )
1615
+ end
1616
+
1617
+ # Called when UNH or UIH encountered
1618
+ #
1619
+ def on_unh_uih( s, tag )
1620
+ end
1621
+
1622
+ # Called when UNT or UIT encountered
1623
+ #
1624
+ def on_unt_uit( s, tag )
1625
+ end
1626
+
1627
+ # Called when any other segment encountered
1628
+ #
1629
+ def on_segment( s, tag )
1630
+ end
1631
+
1632
+ # This callback is usually kept empty. It is called when the parser
1633
+ # finds strings between segments or in front of or trailing an interchange.
1634
+ #
1635
+ # Strictly speaking, such strings are not permitted by the UN/EDIFACT
1636
+ # syntax rules (ISO 9573). However, it is quite common to put a line break
1637
+ # between segments for better readability. The default settings thus
1638
+ # ignore such occurrences.
1639
+ #
1640
+ # If you need strict conformance checking, feel free to put some code
1641
+ # into this callback method, otherwise just ignore it.
1642
+ #
1643
+ #
1644
+ def on_other( s )
1645
+ end
1646
+
1647
+ # Called upon syntax errors. Parsing should be aborted now.
1648
+ #
1649
+ def on_error(err, offset, fragment, c=nil)
1650
+ raise err, "offset = %d, last chars = %s%s" %
1651
+ [offset, fragment, c.nil? ? '<EOF>' : c.chr]
1652
+ end
1653
+
1654
+ #
1655
+ # The one-pass reader & dispatcher of segments, SAX-style.
1656
+ #
1657
+ # It reads sequentially through the given stream of octets and
1658
+ # generates calls to the callbacks <tt>on_...</tt>
1659
+ # Parameter +hnd+ may be any object supporting method +getc+.
1660
+ #
1661
+ def go( hnd )
1662
+ state, offset, iedi, item, tag, una = :outside, 0, false, '', '', ''
1663
+ seg_term, esc_char = nil, ?? # @ic.una.seg_term, @ic.una.esc_char
1664
+ una_count = uib_unb_count = nil
1665
+
1666
+ @path = hnd.path if hnd.respond_to? :path
1667
+
1668
+ self.on_interchange_start
1669
+
1670
+ catch(:done) do
1671
+ loop do
1672
+ c = hnd.getc
1673
+
1674
+ case state # State machine
1675
+
1676
+ # Characters outside of a segment or UNA context
1677
+ when :outside
1678
+ case c
1679
+
1680
+ when nil
1681
+ break # Regular exit at EOF
1682
+
1683
+ when (?A..?Z)
1684
+ unless item.empty? # Flush
1685
+ self.on_other( item )
1686
+ item = ''
1687
+ end
1688
+ item << c; tag << c
1689
+ state = :tag1
1690
+
1691
+ else
1692
+ item << c
1693
+ end
1694
+
1695
+ # Found first tag char, now expecting second
1696
+ when :tag1
1697
+ case c
1698
+
1699
+ when (?A..?Z)
1700
+ item << c; tag << c
1701
+ state = :tag2
1702
+
1703
+ else # including 'nil'
1704
+ self.on_error(EDISyntaxError, offset, item, c)
1705
+ end
1706
+
1707
+ # Found second tag char, now expecting last
1708
+ when :tag2
1709
+ case c
1710
+ when (?A..?Z)
1711
+ item << c; tag << c
1712
+ if tag=='UNA'
1713
+ state = :in_una
1714
+ una_count = 0
1715
+ elsif tag=~/U[IN]B/
1716
+ state = :in_uib_unb
1717
+ uib_unb_count = 0
1718
+ else
1719
+ state = :in_segment
1720
+ end
1721
+ else # including 'nil'
1722
+ self.on_error(EDISyntaxError, offset, item, c)
1723
+ end
1724
+
1725
+ when :in_una
1726
+ self.on_error(EDISyntaxError, offset, item) if c.nil?
1727
+ item << c; una_count += 1
1728
+ if una_count == 6 # completed?
1729
+ esc_char, seg_term = item[6], item[8]
1730
+ self.on_una( item )
1731
+ item, tag = '', ''
1732
+ state = :outside
1733
+ end
1734
+
1735
+ # Set seg_term if version==2 && charset=='UNOB'
1736
+ when :in_uib_unb
1737
+ self.on_error(EDISyntaxError, offset, item) if c.nil?
1738
+ item << c; uib_unb_count += 1
1739
+ if uib_unb_count == 7 # Read up to charset?
1740
+ # Set seg_term if not previously set by UNA
1741
+ if seg_term.nil? && item[4,4]=='UNOB' && item[9]==?2
1742
+ seg_term = ?\x14 # Special case
1743
+ else
1744
+ seg_term = ?' # Default value
1745
+ end
1746
+ state = :in_segment # Continue normally
1747
+ end
1748
+
1749
+ when :in_segment
1750
+ case c
1751
+ when nil
1752
+ self.on_error(EDISyntaxError, offset, item)
1753
+ when esc_char
1754
+ state = :esc_mode
1755
+ when seg_term
1756
+ dispatch_item( item , tag )
1757
+ item, tag = '', ''
1758
+ state = :outside
1759
+ else
1760
+ item << c
1761
+ end
1762
+
1763
+ when :esc_mode
1764
+ case c
1765
+ when nil
1766
+ self.on_error(EDISyntaxError, offset, item)
1767
+ when seg_term # Treat seg_term as regular character
1768
+ item << seg_term
1769
+ # when esc_char # Redundant - skip
1770
+ # item << esc_char << esc_char
1771
+ else
1772
+ item << esc_char << c
1773
+ end
1774
+ state = :in_segment
1775
+
1776
+ else # Should never occur...
1777
+ raise ArgumentError, "unexpected state: #{state}"
1778
+ end
1779
+ offset += 1
1780
+ end # loop
1781
+ # self.on_error(EDISyntaxError, offset, item) unless state==:outside
1782
+ end # catch(:done)
1783
+
1784
+ self.on_interchange_end
1785
+ offset
1786
+ end
1787
+
1788
+ private
1789
+
1790
+ # Private dispatch method to simplify the parser
1791
+
1792
+ def dispatch_item( item, tag ) # :nodoc:
1793
+ case tag
1794
+ when 'UNB', 'UIB'
1795
+ on_unb_uib( item, tag )
1796
+ when 'UNZ', 'UIZ'
1797
+ on_unz_uiz( item, tag )
1798
+ when 'UNG'
1799
+ on_ung( item )
1800
+ when 'UNE'
1801
+ on_une( item )
1802
+ when 'UNH', 'UIH'
1803
+ on_unh_uih( item, tag )
1804
+ when 'UNT', 'UIT'
1805
+ on_unt_uit( item, tag )
1806
+ when /[A-Z]{3}/
1807
+ on_segment( item, tag )
1808
+ else
1809
+ self.on_error(EDISyntaxError, offset, "Illegal tag: #{tag}")
1810
+ end
1811
+ end
1812
+
1813
+ end # StreamingParser
1814
+
1815
+ #########################################################################
1816
+ #
1817
+ # = Class StreamingBuilder
1818
+ #
1819
+ # The StreamingBuilder parses the input stream just like StreamingParser
1820
+ # and in addition builds the complete interchange.
1821
+ #
1822
+ # This method is the new basis of Interchange.parse. You might want to
1823
+ # study its callbacks to get some ideas on how to create a special-purpose
1824
+ # parser/builder of your own.
1825
+ #
1826
+
1827
+ class StreamingBuilder < StreamingParser
1828
+ def initialize(auto_validate=true)
1829
+ @ic = nil
1830
+ @curr_group = @curr_msg = nil
1831
+ @una = nil
1832
+ @is_iedi = false
1833
+ @auto_validate = auto_validate
1834
+ end
1835
+
1836
+
1837
+ def interchange
1838
+ @ic
1839
+ end
1840
+
1841
+
1842
+ def on_una( s )
1843
+ @una = s.dup
1844
+ end
1845
+
1846
+ def on_unb_uib( s, tag ) # Expecting: "UNB+UNOA:3+...", "UIB+UNOC:4..."
1847
+ @ic = Interchange.new( :i_edi => (@is_iedi = tag[1]==?I),
1848
+ :charset => s[4,4],
1849
+ :version => s[9].to_i-?0.to_i, # 1,2,3,4 (int)
1850
+ :una_string => @una )
1851
+ @ic.header = Segment.parse( @ic, s )
1852
+ end
1853
+
1854
+ def on_unz_uiz( s, tag )
1855
+ # FIXME: @is_edi and tag should correspond!
1856
+ @ic.trailer = Segment.parse( @ic, s )
1857
+ end
1858
+
1859
+ def on_ung( s )
1860
+ @curr_group = @ic.new_msggroup( @ic.parse_segment(s,'UNG') )
1861
+ @curr_group.header = Segment.parse( @curr_group, s )
1862
+ end
1863
+
1864
+ def on_une( s )
1865
+ @curr_group.trailer = Segment.parse( @curr_group, s )
1866
+ @ic.add( @curr_group, @auto_validate )
1867
+ end
1868
+
1869
+ def on_unh_uih( s, tag )
1870
+ # FIXME: @is_edi and tag should correspond!
1871
+ seg = @ic.parse_segment(s,tag)
1872
+ @curr_msg = (@curr_group || @ic).new_message( seg )
1873
+ # @curr_msg = (@curr_group || @ic).new_message( @ic.parse_segment(s,tag) )
1874
+ @curr_msg.header = Segment.parse( @curr_msg, s )
1875
+ end
1876
+
1877
+ def on_unt_uit( s, tag )
1878
+ # FIXME: @is_edi and tag should correspond!
1879
+ @curr_msg.trailer = Segment.parse( @curr_msg, s )
1880
+ # puts "on_unt_uit: #@curr_msg"
1881
+ @curr_group.nil? ? @ic.add( @curr_msg, @auto_validate ) : @curr_group.add( @curr_msg )
1882
+ end
1883
+
1884
+ # Overwrite this method to react on segments of interest
1885
+ #
1886
+ # Note: For a skeleton Builder (just UNB/UNG/UNT etc), overwrite with
1887
+ # an empty method.
1888
+ #
1889
+ def on_segment( s, tag )
1890
+ @curr_msg.add @curr_msg.parse_segment( s )
1891
+ super
1892
+ end
1893
+
1894
+
1895
+ def on_interchange_end
1896
+ if @auto_validate
1897
+ @ic.header.validate
1898
+ @ic.trailer.validate
1899
+ # Content is already validated through @ic.add() and @curr_group.add()
1900
+ end
1901
+ end
1902
+
1903
+ end # StreamingBuilder
1904
+
1905
+
1906
+ # Just an idea - not sure it's worth an implementation...
1907
+ #########################################################################
1908
+ #
1909
+ # = Class StreamingSkimmer
1910
+ #
1911
+ # The StreamingSkimmer works as a simplified StreamingBuilder.
1912
+ # It only skims through the service segements of an interchange and
1913
+ # builds an interchange skeleton from them containing just the interchange,
1914
+ # group, and message level, but *not* the regular messages.
1915
+ # Thus, all messages are *empty* and not fit for validation
1916
+ # (use class StreamingBuilder to build a complete interchange).
1917
+ #
1918
+ # StreamingSkimmer lacks an implementation of callback
1919
+ # method <tt>on_segment()</tt>. The interchange skeletons it produces are
1920
+ # thus quicky built and have a small memory footprint.
1921
+ # Customize the class by overwriting <tt>on_segment()</tt>.
1922
+ #
1923
+
1924
+ class StreamingSkimmer < StreamingBuilder
1925
+ def on_segment( s, tag )
1926
+ # Deliberately left empty
1927
+ end
1928
+ end
1929
+
1930
+ end # module EDI::E