edi4r 0.9.4.1 → 0.9.6.2

Sign up to get free protection for your applications and to get access to all the features.
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