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