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.
- checksums.yaml +7 -0
- data/AuthorCopyright +3 -3
- data/{ChangeLog → Changelog} +60 -0
- data/README +15 -10
- data/Tutorial +2 -3
- data/VERSION +1 -1
- data/bin/edi2xml.rb +12 -16
- data/bin/editool.rb +9 -5
- data/bin/sedas2eancom02.rb +1385 -0
- data/bin/xml2edi.rb +7 -12
- data/data/edifact/iso9735/SDCD.20000.csv +1 -0
- data/data/edifact/iso9735/SDCD.3for2.csv +1 -0
- data/data/edifact/iso9735/SDED.20000.csv +6 -0
- data/data/edifact/iso9735/SDED.30000.csv +43 -43
- data/data/edifact/iso9735/SDED.3for2.csv +6 -0
- data/data/edifact/iso9735/SDED.40000.csv +129 -129
- data/data/edifact/iso9735/SDED.40100.csv +130 -130
- data/data/edifact/iso9735/SDMD.20000.csv +6 -0
- data/data/edifact/iso9735/SDMD.30000.csv +6 -6
- data/data/edifact/iso9735/SDMD.3for2.csv +6 -0
- data/data/edifact/iso9735/SDMD.40000.csv +17 -17
- data/data/edifact/iso9735/SDMD.40100.csv +17 -17
- data/data/edifact/iso9735/SDSD.20000.csv +5 -0
- data/data/edifact/iso9735/SDSD.3for2.csv +5 -0
- data/data/edifact/untdid/EDMD.d01b.csv +1 -1
- data/data/sedas/EDCD..csv +0 -0
- data/data/sedas/EDED..csv +859 -0
- data/data/sedas/EDMD..csv +16 -0
- data/data/sedas/EDSD..csv +44 -0
- data/lib/edi4r.rb +147 -67
- data/lib/edi4r/ansi_x12-rexml.rb +91 -0
- data/lib/edi4r/ansi_x12.rb +1684 -0
- data/lib/edi4r/diagrams.rb +75 -14
- data/lib/edi4r/edifact-rexml.rb +4 -3
- data/lib/edi4r/edifact.rb +505 -202
- data/lib/edi4r/rexml.rb +13 -7
- data/lib/edi4r/sedas.rb +854 -0
- data/lib/edi4r/standards.rb +150 -33
- data/test/damaged_file.edi +1 -0
- data/test/eancom2webedi.rb +1 -0
- data/test/groups.edi +1 -1
- data/test/test_basics.rb +16 -9
- data/test/test_edi_split.rb +30 -0
- data/test/test_loopback.rb +7 -2
- data/test/test_rexml.rb +34 -2
- data/test/test_service_messages.rb +190 -0
- data/test/test_streaming.rb +167 -0
- data/test/test_tut_examples.rb +3 -1
- data/test/webedi2eancom.rb +1 -0
- metadata +121 -77
data/lib/edi4r/diagrams.rb
CHANGED
@@ -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 = {
|
107
|
-
|
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
|
-
|
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
|
-
:
|
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
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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:
|
data/lib/edi4r/edifact-rexml.rb
CHANGED
@@ -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
|
data/lib/edi4r/edifact.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
213
|
-
|
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
|
-
'([^',
|
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
|
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'
|
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[:
|
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[:
|
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
|
-
|
483
|
-
|
484
|
-
|
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,
|
590
|
-
|
591
|
-
|
592
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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}/
|
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
|
-
|
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
|