edi4r 0.9.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/AuthorCopyright +10 -0
- data/COPYING +56 -0
- data/ChangeLog +106 -0
- data/README +66 -0
- data/TO-DO +35 -0
- data/Tutorial +609 -0
- data/VERSION +1 -0
- data/bin/edi2xml.rb +103 -0
- data/bin/editool.rb +151 -0
- data/bin/xml2edi.rb +50 -0
- data/data/edifact/iso9735/SDCD.10000.csv +10 -0
- data/data/edifact/iso9735/SDCD.20000.csv +10 -0
- data/data/edifact/iso9735/SDCD.30000.csv +11 -0
- data/data/edifact/iso9735/SDCD.40000.csv +31 -0
- data/data/edifact/iso9735/SDCD.40100.csv +31 -0
- data/data/edifact/iso9735/SDED.10000.csv +37 -0
- data/data/edifact/iso9735/SDED.20000.csv +37 -0
- data/data/edifact/iso9735/SDED.30000.csv +43 -0
- data/data/edifact/iso9735/SDED.40000.csv +129 -0
- data/data/edifact/iso9735/SDED.40100.csv +130 -0
- data/data/edifact/iso9735/SDMD.10000.csv +0 -0
- data/data/edifact/iso9735/SDMD.20000.csv +0 -0
- data/data/edifact/iso9735/SDMD.30000.csv +6 -0
- data/data/edifact/iso9735/SDMD.40000.csv +17 -0
- data/data/edifact/iso9735/SDMD.40100.csv +17 -0
- data/data/edifact/iso9735/SDSD.10000.csv +8 -0
- data/data/edifact/iso9735/SDSD.20000.csv +8 -0
- data/data/edifact/iso9735/SDSD.30000.csv +12 -0
- data/data/edifact/iso9735/SDSD.40000.csv +34 -0
- data/data/edifact/iso9735/SDSD.40100.csv +34 -0
- data/data/edifact/untdid/EDCD.d01b.csv +200 -0
- data/data/edifact/untdid/EDCD.d96a.csv +161 -0
- data/data/edifact/untdid/EDED.d01b.csv +641 -0
- data/data/edifact/untdid/EDED.d96a.csv +462 -0
- data/data/edifact/untdid/EDMD.d01b.csv +3419 -0
- data/data/edifact/untdid/EDMD.d96a.csv +2144 -0
- data/data/edifact/untdid/EDSD.d01b.csv +158 -0
- data/data/edifact/untdid/EDSD.d96a.csv +127 -0
- data/data/edifact/untdid/IDCD.d01b.csv +95 -0
- data/data/edifact/untdid/IDMD.d01b.csv +238 -0
- data/data/edifact/untdid/IDSD.d01b.csv +75 -0
- data/lib/edi4r.rb +928 -0
- data/lib/edi4r/diagrams.rb +567 -0
- data/lib/edi4r/edi4r-1.2.dtd +20 -0
- data/lib/edi4r/edifact-rexml.rb +221 -0
- data/lib/edi4r/edifact.rb +1627 -0
- data/lib/edi4r/rexml.rb +256 -0
- data/lib/edi4r/standards.rb +495 -0
- data/test/eancom2webedi.rb +380 -0
- data/test/groups.edi +1 -0
- data/test/in1.edi +1 -0
- data/test/in1.inh +3 -0
- data/test/in2.edi +1 -0
- data/test/in2.xml +350 -0
- data/test/test_basics.rb +209 -0
- data/test/test_edi_split.rb +53 -0
- data/test/test_loopback.rb +21 -0
- data/test/test_minidemo.rb +84 -0
- data/test/test_rexml.rb +98 -0
- data/test/test_tut_examples.rb +131 -0
- data/test/webedi2eancom.rb +408 -0
- metadata +110 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
AAI;ACCOMMODATION ALLOCATION INFORMATION;010;E997;M;20;
|
2
|
+
ADI;HEALTH CARE CLAIM ADJUDICATION INFORMATION;010;E206;M;3;020;E021;C;3;030;1229;C;1;040;E017;C;15;050;6060;C;3;060;E030;C;15;070;9620;C;4;080;5389;C;1;090;E507;C;1;100;9639;C;1;110;5482;C;1;
|
3
|
+
ADS;ADDRESS;010;E817;C;1;020;E001;C;1;030;3164;C;1;040;3251;C;1;050;3207;C;1;060;E819;C;1;070;E517;C;1;
|
4
|
+
ALS;ADDITIONAL LOCATION INFORMATION;010;3227;M;1;020;E975;M;99;030;6000;C;1;040;6002;C;1;
|
5
|
+
APD;ADDITIONAL TRANSPORT DETAILS;010;E961;C;1;020;E962;C;2;030;E963;C;1;040;E964;C;1;050;E965;C;10;
|
6
|
+
ASD;SERVICE DETAILS;010;E959;M;9;020;E013;C;99;030;2160;C;1;
|
7
|
+
ATI;TOUR INFORMATION;010;E993;C;1;020;E994;C;99;
|
8
|
+
ATR;ATTRIBUTE;010;9017;M;1;020;E003;M;1;
|
9
|
+
BLI;BILLABLE INFORMATION;010;5004;C;3;020;E029;C;9;030;E507;C;9;040;7402;C;5;050;9607;C;3;060;E028;C;9;
|
10
|
+
CLT;CLEAR TERMINATE INFORMATION;010;1229;M;1;020;1225;C;2;030;4440;C;1;
|
11
|
+
CMN;COMMISSION INFORMATION;010;E002;M;9;
|
12
|
+
CNX;CONNECTION DETAILS;010;E999;C;9;
|
13
|
+
CNY;COUNTRY INFORMATION;010;3207;M;1;020;E013;C;2;030;2031;C;2;040;6345;C;1;050;3453;C;9;
|
14
|
+
CON;CONTACT INFORMATION;010;E966;C;20;
|
15
|
+
CRI;CONSUMER REFERENCE INFORMATION;010;E967;M;20;
|
16
|
+
CUR;CURRENCIES;010;E504;C;1;020;E504;C;1;030;5402;C;1;040;6341;C;1;
|
17
|
+
DAV;DAILY AVAILABILITY;010;7037;M;1;020;E009;M;31;
|
18
|
+
DIS;DISCOUNT INFORMATION;010;E998;M;20;
|
19
|
+
DNT;DENTAL INFORMATION;010;7402;C;1;020;7383;C;5;030;9647;C;5;040;6060;C;6;050;4405;C;1;
|
20
|
+
DTI;DATE AND TIME INFORMATION;010;E013;M;9;020;E014;C;99;
|
21
|
+
ERI;APPLICATION ERROR INFORMATION;010;E901;M;1;
|
22
|
+
FRM;FOLLOW-UP ACTION;010;7402;M;1;020;9013;M;7;030;1229;C;1;
|
23
|
+
FRQ;FREQUENCY;010;E520;M;99;
|
24
|
+
FTI;FREQUENT TRAVELLER INFORMATION;010;E970;M;9;
|
25
|
+
HDI;HARDWARE DEVICE INFORMATION;010;3148;C;1;020;3413;C;1;030;1511;C;1;040;E003;C;9;
|
26
|
+
HDR;HEADER INFORMATION;010;4405;M;1;020;E013;M;4;030;1154;C;1;040;4440;C;1;050;7135;C;2;060;3453;C;1;
|
27
|
+
ICI;INSURANCE COVER INFORMATION;010;E016;M;1;020;E017;C;5;
|
28
|
+
IFT;INTERACTIVE FREE TEXT;010;E971;C;1;020;4440;C;99;
|
29
|
+
ITC;INSTITUTIONAL CLAIM;010;E027;M;1;020;6060;M;4;030;E026;M;1;040;9447;M;1;050;E025;C;9;
|
30
|
+
ITD;INFORMATION TYPE DATA;010;9601;C;1;020;3453;C;1;030;9603;C;10;040;1503;C;1;
|
31
|
+
ITM;ITEM NUMBER;010;E212;C;99;
|
32
|
+
LKP;LEVEL INDICATION;010;E778;M;1;
|
33
|
+
LNG;LANGUAGE;010;3455;M;1;020;E033;C;1;
|
34
|
+
MAP;MESSAGE APPLICATION PRODUCT INFORMATION;010;E022;C;1;020;3036;C;1;030;E031;C;1;
|
35
|
+
MES;MEASUREMENTS;010;E175;M;9;
|
36
|
+
MOV;CAR DELIVERY INSTRUCTION;010;E995;C;1;020;6350;C;1;030;3453;C;1;
|
37
|
+
MSD;MESSAGE ACTION DETAILS;010;E972;C;1;020;4343;C;5;030;E206;C;5;
|
38
|
+
NAA;NAME AND ADDRESS;010;3035;M;1;020;E082;C;1;030;3124;C;5;040;E080;C;1;050;3042;C;1;060;3164;C;1;070;3229;C;1;080;3251;C;1;090;3207;C;1;
|
39
|
+
NME;NAME;010;E012;M;9;
|
40
|
+
NUN;NUMBER OF UNITS;010;E523;M;9;
|
41
|
+
ODI;ORIGIN AND DESTINATION DETAILS;010;3225;C;2;020;1050;C;2;
|
42
|
+
ODS;ADDITIONAL PRODUCT DETAILS;010;9605;M;1;020;E015;M;999;
|
43
|
+
ORG;ORIGINATOR OF REQUEST DETAILS;010;E973;C;1;020;E974;C;1;030;E975;C;1;040;3036;C;1;050;3457;C;1;060;E976;C;1;070;3503;C;1;
|
44
|
+
OTI;OTHER INSURANCE;010;E206;M;5;020;9645;M;1;030;E507;C;2;040;5267;C;10;050;5004;C;4;060;E030;C;15;070;4497;C;1;080;9143;C;1;090;9607;C;1;
|
45
|
+
PDT;PRODUCT INFORMATION;010;7133;C;1;020;E996;C;26;
|
46
|
+
PLI;PRODUCT LOCATION INFORMATION;010;E008;M;99;
|
47
|
+
PMT;PAYMENT INFORMATION;010;E977;C;99;020;E978;C;99;
|
48
|
+
POP;PERIOD OF OPERATION;010;E013;M;1;020;2160;C;1;030;4405;C;1;
|
49
|
+
POR;LOCATION AND/OR RELATED TIME INFORMATION;010;E517;M;1;020;E362;C;2;030;E992;C;2;040;3227;C;1;050;1050;C;1;
|
50
|
+
POS;POINT OF SALE INFORMATION;010;E032;C;999;020;E975;C;999;
|
51
|
+
PRD;PRODUCT IDENTIFICATION;010;E989;C;9;020;3036;C;6;
|
52
|
+
PRE;PRICE DETAILS;010;E018;C;20;020;5213;C;1;
|
53
|
+
PRO;PROMOTIONS;010;E019;M;20;
|
54
|
+
PRT;PARTY INFORMATION;010;3035;M;1;020;E206;C;9;030;2000;C;3;040;E023;C;1;
|
55
|
+
PSI;SERVICE INFORMATION;010;7402;M;4;020;E507;C;5;030;E021;C;1;040;6060;C;2;050;5004;C;5;060;5030;C;4;070;9039;C;1;080;5267;C;1;090;4183;C;9;100;E024;C;1;
|
56
|
+
QTI;QUANTITY;010;E035;M;9;
|
57
|
+
RCI;RESERVATION CONTROL INFORMATION;010;E979;C;99;
|
58
|
+
RFR;REFERENCE;010;E506;M;1;
|
59
|
+
RLS;RELATIONSHIP;010;9141;M;1;020;E941;C;1;
|
60
|
+
RPI;QUANTITY AND ACTION DETAILS;010;E958;M;9;
|
61
|
+
RTC;RATE TYPES;010;5263;M;99;
|
62
|
+
RTI;RATE DETAILS;010;E011;M;99;
|
63
|
+
RUL;RULE INFORMATION;010;E004;C;99;020;E005;C;9;030;E006;C;9;
|
64
|
+
SDT;SELECTION DETAILS;010;E010;M;99;
|
65
|
+
SER;FACILITY INFORMATION;010;E965;C;99;020;1229;C;1;030;6350;C;1;040;E013;C;99;050;2160;C;1;
|
66
|
+
SSR;SPECIAL REQUIREMENT DETAILS;010;E980;M;1;020;E981;C;999;
|
67
|
+
TCE;TIME AND CERTAINTY;010;2380;C;1;020;E946;C;1;
|
68
|
+
TDI;TRAVELLER DOCUMENT INFORMATION;010;E968;M;1;020;E969;C;1;030;3500;C;1;040;3460;C;99;
|
69
|
+
TFF;TARIFF INFORMATION;010;E982;C;99;020;E983;C;99;030;E984;C;99;
|
70
|
+
TIF;TRAVELLER INFORMATION;010;E985;M;1;020;E986;C;99;
|
71
|
+
TIZ;TIME ZONE INFORMATION;010;E034;M;1;
|
72
|
+
TRF;TRAFFIC RESTRICTION DETAILS;010;E007;M;5;
|
73
|
+
TVL;TRAVEL PRODUCT INFORMATION;010;E987;C;1;020;E975;C;2;030;E988;C;1;040;E989;C;1;050;E990;C;1;060;1082;C;1;070;7365;C;1;
|
74
|
+
TXS;TAXES;010;E020;M;99;
|
75
|
+
VEH;VEHICLE;010;8053;C;1;020;E991;C;1;030;E211;C;1;040;6314;C;1;050;E992;C;1;060;1145;C;1;
|
data/lib/edi4r.rb
ADDED
@@ -0,0 +1,928 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
:main:README
|
3
|
+
:title:edi4r
|
4
|
+
|
5
|
+
= UN/EDIFACT module
|
6
|
+
|
7
|
+
An API to parse and create UN/EDIFACT and other EDI data
|
8
|
+
* Abstract classes are maintained in this file.
|
9
|
+
* See other files in edi4r/ for specific EDI syntax standards.
|
10
|
+
|
11
|
+
$Id: edi4r.rb,v 1.6 2006/08/01 11:12:39 werntges Exp $
|
12
|
+
|
13
|
+
:include: ../AuthorCopyright
|
14
|
+
|
15
|
+
== Background
|
16
|
+
|
17
|
+
We anticipate to support several EDI syntax standards with this module:
|
18
|
+
|
19
|
+
C Name Description
|
20
|
+
===========================================================================
|
21
|
+
A ANSI ASC X.12 the U.S. EDI standard; a precursor of UN/EDIFACT
|
22
|
+
E UN/EDIFACT the global EDI standard under the auspices of the UNO
|
23
|
+
G GENCOD an early French EDI standard, consumer goods branch
|
24
|
+
I SAP IDoc not an EDI standard, but a very popular in-house format
|
25
|
+
S SEDAS an early Austrian/German EDI standard, see GENCOD
|
26
|
+
T Tradacoms the British EDI standard; a precursor of UN/EDIFACT
|
27
|
+
X XML (DTDs / Schemas still to be supplied)
|
28
|
+
|
29
|
+
Our focus will be on UN/EDIFACT, the only global EDI standard we have
|
30
|
+
that is independent of industry branches.
|
31
|
+
|
32
|
+
Terms used will be borrowed from EDIFACT and applied to the other
|
33
|
+
syntax standards whenever possible.
|
34
|
+
|
35
|
+
A, E, and T are technically related in that they employ a compact
|
36
|
+
data representation based on a hierarchy of separator characters.
|
37
|
+
G and S as well as I are fixed-record formats, X is a markup syntax.
|
38
|
+
|
39
|
+
== Data model
|
40
|
+
|
41
|
+
We use the EDIFACT model as the name-giving, most general model.
|
42
|
+
Other EDI standards might not support all features.
|
43
|
+
|
44
|
+
The basic unit exchanged between trading partners is the "Interchange".
|
45
|
+
An interchange consists of an envelope and content. Content is
|
46
|
+
either a sequence of messages or a sequence of message groups.
|
47
|
+
Message groups - if used - comprise a (group level) envelope and
|
48
|
+
a sequence of messages.
|
49
|
+
|
50
|
+
A message is a sequence of segments (sometimes also called records).
|
51
|
+
A segment consists of a sequence of data elements (aka. fields),
|
52
|
+
either simple ones (DE) or composites (CDE).
|
53
|
+
Composites are sequences of simple data elements.
|
54
|
+
|
55
|
+
Hence:
|
56
|
+
|
57
|
+
Interchange > [ MsgGroup > ] Message > Segment > [ CDE > ] DE
|
58
|
+
|
59
|
+
Syntax related information is maintained at the top (i.e. interchange) level.
|
60
|
+
Lower-level objects like segments and DEs are aware of their syntax context
|
61
|
+
through attibute "root", even though this context originates at the
|
62
|
+
interchange level.
|
63
|
+
|
64
|
+
Lower levels may add information. E.g. a message may add its message type,
|
65
|
+
or a segment its hierarchy level, and its segment group - depending
|
66
|
+
on the syntax standard in use.
|
67
|
+
|
68
|
+
This basic structure is always maintained, even in cases like SAP IDocs
|
69
|
+
where the Interchange level is just an abstraction.
|
70
|
+
|
71
|
+
Note that this data model describes the data as they are parsed or built,
|
72
|
+
essentially lists of lists or strings. In contrast, EDI documents
|
73
|
+
frequently publish specifications in a hierarchical way, using terms like
|
74
|
+
"segment group", "instance", "level" and alike.
|
75
|
+
Here we regard such information as metadata, or additional properties
|
76
|
+
which may or may not apply or be required.
|
77
|
+
|
78
|
+
=== Example
|
79
|
+
|
80
|
+
You can build a valid EDIFACT interchange simply by adding
|
81
|
+
messages and segments - just follow your specifications.
|
82
|
+
|
83
|
+
However, if you want this Ruby module to *validate* your result,
|
84
|
+
the metadata are required. Similarly, in order to map from EDIFACT to
|
85
|
+
other formats, accessing inbound segments though their hierarchical
|
86
|
+
representation is much more convenient than processing them linearly.
|
87
|
+
|
88
|
+
== EDI Class hierarchy (overview)
|
89
|
+
|
90
|
+
EDI_Object:: Collection, DE
|
91
|
+
Collection:: Collection_HT, Collection_S
|
92
|
+
Collection_HT:: Interchange, MsgGroup, Message
|
93
|
+
Collection_S:: Segment, CDE
|
94
|
+
=end
|
95
|
+
|
96
|
+
# To-do list:
|
97
|
+
# validate - add still more functionality, e.g. codelists
|
98
|
+
# charset - check for valid chars in more charsets
|
99
|
+
# NDB - support codelists
|
100
|
+
# (much more, including general cleanup & tuning ...)
|
101
|
+
|
102
|
+
require 'enumerator'
|
103
|
+
|
104
|
+
BEGIN {
|
105
|
+
require 'pathname'
|
106
|
+
# 'realpath' fails on Windows platforms!
|
107
|
+
# ENV['EDI_NDB_PATH'] = Pathname.new(__FILE__).dirname.parent.realpath + 'data'
|
108
|
+
pdir = Pathname.new(__FILE__).dirname.parent
|
109
|
+
ENV['EDI_NDB_PATH'] = File.expand_path(pdir) + File::Separator + 'data'
|
110
|
+
}
|
111
|
+
|
112
|
+
require "edi4r/standards"
|
113
|
+
require "edi4r/diagrams"
|
114
|
+
|
115
|
+
|
116
|
+
module EDI
|
117
|
+
|
118
|
+
#########################################################################
|
119
|
+
#
|
120
|
+
# Basic (abstract) class: Makes sure that all derived
|
121
|
+
# EDI objects have at least following attributes:
|
122
|
+
#
|
123
|
+
# +parent+:: Reference to parent EDI object (a +Collection+)
|
124
|
+
# +root+:: Reference to root EDI object (typically an +Interchange+)
|
125
|
+
# +name+:: The name of this instance (a +String+ object)
|
126
|
+
#
|
127
|
+
# Caveat:: Setters are used only internally during message construction.
|
128
|
+
# Avoid using them!
|
129
|
+
|
130
|
+
|
131
|
+
class Object
|
132
|
+
attr_accessor :parent, :root, :name
|
133
|
+
|
134
|
+
def initialize (parent, root, name)
|
135
|
+
@parent, @root, @name = parent, root, name
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
#########################################################################
|
141
|
+
#
|
142
|
+
# A simple utility class that fills a need not covered by "zlib".
|
143
|
+
#
|
144
|
+
# It is stripped to the essentials needed here internally.
|
145
|
+
# Not recommended for general use! The overhead of starting
|
146
|
+
# "bzcat" processes all the time is considerable, binding to a library
|
147
|
+
# similar to 'zib' for the BZIP2 format would give much better results.
|
148
|
+
|
149
|
+
class Bzip2Reader
|
150
|
+
attr_accessor :path
|
151
|
+
|
152
|
+
def initialize( hnd )
|
153
|
+
@path = hnd.path
|
154
|
+
@pipe = IO.popen("bzcat #@path",'r' )
|
155
|
+
end
|
156
|
+
|
157
|
+
def read( len=0 )
|
158
|
+
len==0 ? @pipe.read : @pipe.read( len )
|
159
|
+
end
|
160
|
+
|
161
|
+
def rewind
|
162
|
+
@pipe.close
|
163
|
+
@pipe = IO.popen("bzcat #@path",'r' )
|
164
|
+
end
|
165
|
+
|
166
|
+
def close
|
167
|
+
@pipe.close
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#########################################################################
|
172
|
+
#
|
173
|
+
# An EDI collection instance behaves like a simplified array.
|
174
|
+
# In addition, it permits access to its elements through their names.
|
175
|
+
# This implies that only objects with a +name+ may be stored,
|
176
|
+
# i.e. derivatives of EDI::Object.
|
177
|
+
|
178
|
+
class Collection < EDI::Object
|
179
|
+
|
180
|
+
def initialize( parent, root, name )
|
181
|
+
super
|
182
|
+
@a = []
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def root= (rt)
|
187
|
+
super( rt )
|
188
|
+
each {|obj| obj.root = rt }
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
# Similar to Array#push(), but automatically setting obj's
|
193
|
+
# parent and root to self and self's root. Returns obj.
|
194
|
+
def add( obj )
|
195
|
+
push obj
|
196
|
+
obj.parent = self
|
197
|
+
obj.root = self.root
|
198
|
+
obj
|
199
|
+
end
|
200
|
+
|
201
|
+
alias append add
|
202
|
+
|
203
|
+
|
204
|
+
def ==(obj)
|
205
|
+
self.object_id == obj.object_id
|
206
|
+
end
|
207
|
+
|
208
|
+
# Delegate to array:
|
209
|
+
# index, each, find_all, length, size, first, last
|
210
|
+
def index(obj); @a.index(obj); end
|
211
|
+
def each(&b); @a.each(&b); end
|
212
|
+
def find_all(&b); @a.find_all(&b); end
|
213
|
+
def size; @a.size; end
|
214
|
+
def length; @a.length; end
|
215
|
+
def first; @a.first; end
|
216
|
+
def last; @a.last; end
|
217
|
+
|
218
|
+
# The element reference operator [] supports two access modes:
|
219
|
+
# Array-like:: Return indexed element when passing an integer
|
220
|
+
# By name:: Return array of element(s) whose name(s) match given string
|
221
|
+
def [](i)
|
222
|
+
lookup(i)
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
# This implementation of +inspect()+ is very verbose in that it
|
227
|
+
# inspects also all contained objects in a recursive manner.
|
228
|
+
#
|
229
|
+
# indent:: String offset to use for indentation / pretty-printing
|
230
|
+
# symlist:: Array of getter names (passed as symbols) whose values are
|
231
|
+
# to be listed. Note that :name is included automatically.
|
232
|
+
|
233
|
+
def inspect( indent='', symlist=[] )
|
234
|
+
headline = indent + self.name+': ' + symlist.map do |sym|
|
235
|
+
"#{sym} = #{(s=send(sym)).nil? ? 'nil' : s.to_s}"
|
236
|
+
end.join(', ') + "\n"
|
237
|
+
headline << @header.inspect(indent+' ') if @header
|
238
|
+
s = @a.inject( headline ){|s,obj| s << obj.inspect(indent+' ')}
|
239
|
+
@trailer ? s << @trailer.inspect(indent+' ') : s
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
# Returns an array of names of all included objects in proper sequence;
|
244
|
+
# primarily for internal use.
|
245
|
+
|
246
|
+
def names
|
247
|
+
@a.collect {|e| e.name}
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
# Helper method: Turns e.g. "EDI::E::Interchange" into "Interchange".
|
252
|
+
# For internal use only!
|
253
|
+
|
254
|
+
def normalized_class_name # :nodoc:
|
255
|
+
if self.class.to_s !~ /^(\w*::)?(\w::)(\w+)?$/
|
256
|
+
raise "Cannot normalize class name: #{self.class.to_s}"
|
257
|
+
end
|
258
|
+
$3
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
# push: Similar to Array#push, except that it requires objects
|
265
|
+
# with getter :name
|
266
|
+
# Low-level method, avoid. Use "add" instead.
|
267
|
+
|
268
|
+
def push( obj )
|
269
|
+
raise TypeError unless obj.is_a? EDI::Object # obj.respond_to? :name
|
270
|
+
@a << obj
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
alias << push
|
275
|
+
|
276
|
+
|
277
|
+
def lookup(i)
|
278
|
+
if i.is_a?(Integer)
|
279
|
+
@a[i]
|
280
|
+
else
|
281
|
+
@a.find_all {|x| x.name == i}
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Here we perform the "magic" that provides us with dynamically
|
286
|
+
# "generated" getters and setters for just those DE and CDE
|
287
|
+
# available in the given Collection_S instance.
|
288
|
+
#
|
289
|
+
# UN/EDIFACT examples:
|
290
|
+
# d3055, d1004=(value), cC105, a7174[1].value
|
291
|
+
|
292
|
+
def method_missing(sym, *par)
|
293
|
+
if sym.id2name =~ /^([acds])(\w+)(=)?/
|
294
|
+
rc = lookup($2)
|
295
|
+
if rc.is_a? Array
|
296
|
+
if rc.size==1
|
297
|
+
rc = rc.first
|
298
|
+
elsif rc.size==0
|
299
|
+
return super
|
300
|
+
end
|
301
|
+
end
|
302
|
+
if $3
|
303
|
+
# Setter
|
304
|
+
raise TypeError, "Can't assign to array #$2" if rc.is_a? Array
|
305
|
+
raise TypeError, "Can only assign to a DE value" if $1 != 'd'
|
306
|
+
rc.value = par[0]
|
307
|
+
else
|
308
|
+
# Getter
|
309
|
+
return rc.value if rc.is_a? DE and $1 == 'd'
|
310
|
+
return rc if rc.is_a? CDE and $1 == 'c'
|
311
|
+
return rc if rc.is_a? Segment and $1 == 's'
|
312
|
+
err_msg = "Method prefix '#$1' not matching result '#{rc.class}'!"
|
313
|
+
raise TypeError, err_msg unless rc.is_a? Array
|
314
|
+
# Don't let whole DEs be overwritten - enforce usage of "value":
|
315
|
+
rc.freeze
|
316
|
+
return rc if $1 == 'a'
|
317
|
+
raise TypeError,"Array found - use 'a#$2[i]' to access component i"
|
318
|
+
end
|
319
|
+
else
|
320
|
+
super
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
#########################################################################
|
328
|
+
#
|
329
|
+
# A collection with header and trailer, common to Interchange, MsgGroup,
|
330
|
+
# and Message. Typically, header and trailer are Segment instances.
|
331
|
+
#
|
332
|
+
class Collection_HT < Collection
|
333
|
+
attr_accessor :header, :trailer # make private or protected?
|
334
|
+
|
335
|
+
def root= (rt)
|
336
|
+
super( rt )
|
337
|
+
@header.root = rt if @header
|
338
|
+
@trailer.root = rt if @trailer
|
339
|
+
end
|
340
|
+
|
341
|
+
|
342
|
+
# Experimental: Ignore content of header / trailer,
|
343
|
+
# regard object as empty when nothing "add"ed to it.
|
344
|
+
|
345
|
+
def empty?
|
346
|
+
@a.empty?
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
def validate( err_count=0 )
|
351
|
+
err_count += @header.validate if @header
|
352
|
+
err_count += @trailer.validate if @trailer
|
353
|
+
each {|obj| err_count += obj.validate}
|
354
|
+
err_count
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
def to_s( postfix='' )
|
359
|
+
s = @header ? @header.to_s+postfix : ''
|
360
|
+
each {|obj| s << (obj.is_a?(Segment) ? obj.to_s+postfix : obj.to_s)}
|
361
|
+
s << @trailer.to_s+postfix if @trailer
|
362
|
+
s
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
#########################################################################
|
369
|
+
#
|
370
|
+
# A "segment-like" collection, base class of Segment and CDE
|
371
|
+
#
|
372
|
+
class Collection_S < Collection
|
373
|
+
attr_accessor :status, :rep, :maxrep
|
374
|
+
|
375
|
+
|
376
|
+
def initialize(parent, name, status=nil)
|
377
|
+
@status = status
|
378
|
+
super( parent, parent.root, name)
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
def validate( err_count=0 )
|
383
|
+
location = "#{parent.name} - #{@name}"
|
384
|
+
if empty?
|
385
|
+
if required?
|
386
|
+
warn "#{location}: Empty though mandatory!"
|
387
|
+
err_count += 1
|
388
|
+
end
|
389
|
+
else
|
390
|
+
if rep && maxrep && rep > maxrep
|
391
|
+
warn "#{location}: Too often repeated: #{rep} > #{maxrep}!"
|
392
|
+
err_count += 1
|
393
|
+
end
|
394
|
+
each {|obj| err_count += obj.validate}
|
395
|
+
end
|
396
|
+
err_count
|
397
|
+
end
|
398
|
+
|
399
|
+
|
400
|
+
def fmt_of_DE(id) # :nodoc:
|
401
|
+
@parent.fmt_of_DE(id)
|
402
|
+
end
|
403
|
+
|
404
|
+
|
405
|
+
def each_BCDS(id, &b) # :nodoc:
|
406
|
+
@parent.each_BCDS(id, &b)
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
# Returns +true+ if all contained elements are empty.
|
411
|
+
|
412
|
+
def empty?
|
413
|
+
empty = true
|
414
|
+
each {|obj| empty &= obj.empty? } # DE or CDE
|
415
|
+
empty
|
416
|
+
end
|
417
|
+
|
418
|
+
|
419
|
+
# Returns +true+ if this segment or CDE is mandatory / required
|
420
|
+
# according to its defining "Diagram".
|
421
|
+
|
422
|
+
def required?
|
423
|
+
@status == 'M' or @status == 'R'
|
424
|
+
end
|
425
|
+
|
426
|
+
|
427
|
+
def inspect( indent='', symlist=[] )
|
428
|
+
symlist += [:status, :rep, :maxrep]
|
429
|
+
super
|
430
|
+
end
|
431
|
+
|
432
|
+
end
|
433
|
+
|
434
|
+
|
435
|
+
#########################################################################
|
436
|
+
#
|
437
|
+
# Base class of all interchanges
|
438
|
+
#
|
439
|
+
class Interchange < Collection_HT
|
440
|
+
|
441
|
+
attr_accessor :output_mode
|
442
|
+
attr_reader :syntax, :version, :output_mode, :illegal_charset_pattern
|
443
|
+
|
444
|
+
# Abstract class - don't instantiate directly
|
445
|
+
#
|
446
|
+
def initialize( user_par=nil )
|
447
|
+
super( nil, self, 'Interchange' )
|
448
|
+
@illegal_charset_pattern = /^$/ # Default: Never match a non-empty string
|
449
|
+
@content = nil # nil if empty, :messages, or :groups
|
450
|
+
end
|
451
|
+
|
452
|
+
# Auto-detect file content, optionally decompress, return an
|
453
|
+
# Interchange object of the sub-class that matches the (unzipped) content.
|
454
|
+
#
|
455
|
+
# This is a convenience method.
|
456
|
+
# When you know the file contents, consider a direct call to
|
457
|
+
# Interchange::E::parse etc.
|
458
|
+
#
|
459
|
+
# NOTES:
|
460
|
+
# * Make sure to <tt>require 'zlib'</tt> when applying this method
|
461
|
+
# to gzipped files.
|
462
|
+
# * BZIP2 is indirectly supported by calling "bzcat". Make sure that
|
463
|
+
# "bzcat" is available when applying this method to *.bz2 files.
|
464
|
+
# * Do not pass $stdin to this method - we could not "rewind" it!
|
465
|
+
|
466
|
+
def Interchange.parse( hnd, auto_validate=true )
|
467
|
+
case rc=Interchange.detect( hnd )
|
468
|
+
when 'BZ': Interchange.parse( EDI::Bzip2Reader.new( hnd ) ) # see "peek"
|
469
|
+
when 'GZ': Interchange.parse( Zlib::GzipReader.new( hnd ) )
|
470
|
+
when 'E': EDI::E::Interchange.parse( hnd, auto_validate )
|
471
|
+
when 'I': EDI::I::Interchange.parse( hnd, auto_validate )
|
472
|
+
when 'XE': EDI::E::Interchange.parse_xml( REXML::Document.new(hnd) )
|
473
|
+
when 'XI': EDI::I::Interchange.parse_xml( REXML::Document.new(hnd) )
|
474
|
+
else raise "#{rc}: Unsupported format key - don\'t know how to proceed!"
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# Auto-detect file content, optionally decompress, return an
|
479
|
+
# empty Interchange object of that sub-class with only the header filled.
|
480
|
+
#
|
481
|
+
# This is a convenience method.
|
482
|
+
# When you know the file contents, consider a direct call to
|
483
|
+
# Interchange::E::peek etc.
|
484
|
+
#
|
485
|
+
# NOTES: See Interchange.parse
|
486
|
+
|
487
|
+
def Interchange.peek( hnd=$stdin)
|
488
|
+
case rc=Interchange.detect( hnd )
|
489
|
+
# Does not exist yet!
|
490
|
+
# when 'BZ': Interchange.peek( Zlib::Bzip2Reader.new( hnd ) )
|
491
|
+
# Temporary substitute, Unix/Linux only, low performance:
|
492
|
+
when 'BZ': Interchange.peek( EDI::Bzip2Reader.new( hnd ) )
|
493
|
+
|
494
|
+
when 'GZ': Interchange.peek( Zlib::GzipReader.new( hnd ) )
|
495
|
+
when 'E': EDI::E::Interchange.peek( hnd )
|
496
|
+
when 'I': EDI::I::Interchange.peek( hnd )
|
497
|
+
when 'XE': EDI::E::Interchange.peek_xml( REXML::Document.new(hnd) )
|
498
|
+
when 'XI': EDI::I::Interchange.peek_xml( REXML::Document.new(hnd) )
|
499
|
+
else raise "#{rc}: Unsupported format key - don\'t know how to proceed!"
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Auto-detect the given file format & content, return format key
|
504
|
+
#
|
505
|
+
# Convenience method, intended for internal use only
|
506
|
+
#
|
507
|
+
def Interchange.detect( hnd ) # :nodoc:
|
508
|
+
buf = hnd.read( 256 )
|
509
|
+
#
|
510
|
+
# NOTE: "rewind" fails when applied to $stdin!
|
511
|
+
# If you really need to read from $stdin, call Interchange::E::parse()
|
512
|
+
# and Interchange::E::peek() etc. directly to bypass auto-detection
|
513
|
+
hnd.rewind
|
514
|
+
|
515
|
+
re = /(<\?xml.*?)?DOCTYPE\s+Interchange.*?\<Interchange\s+.*?standard\_key\s*=\s*(['"])(.)\2/m
|
516
|
+
case buf
|
517
|
+
when /^(UNA......)?\r?\n?U[IN]B.UNO[A-Z].[1-4]/: 'E' # UN/EDIFACT
|
518
|
+
when /^EDI_DC/: 'I' # SAP IDoc
|
519
|
+
when re : 'X'+$3 # XML, Doctype = Interchange, syntax standard key (E, I, ...) postfix
|
520
|
+
when /^\037\213/: 'GZ' # gzip
|
521
|
+
when /^\037\235/: 'Z' # compress
|
522
|
+
when /^\037\036/: 'z' # pack
|
523
|
+
when /^BZh[0-\377]/: 'BZ' # bzip2
|
524
|
+
else; "?? (stream starts with: #{buf[0..15]})"
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
|
529
|
+
def fmt_of_DE(id) # :nodoc:
|
530
|
+
de = @basedata.de(id)
|
531
|
+
de.nil? ? nil : de.format
|
532
|
+
end
|
533
|
+
|
534
|
+
|
535
|
+
def each_BCDS(id, &b) # :nodoc:
|
536
|
+
begin
|
537
|
+
@basedata.each_BCDS(id, &b )
|
538
|
+
rescue EDILookupError # NoMethodError
|
539
|
+
raise "Lookup failure for BCDS entry id '#{id}'"
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
|
544
|
+
# Add either Message objects or MsgGroup objects to an interchange;
|
545
|
+
# mixing both types raises a TypeError.
|
546
|
+
|
547
|
+
def add( obj, auto_validate=true )
|
548
|
+
err_msg = "Added object must also be a "
|
549
|
+
if obj.is_a? Message
|
550
|
+
@content = :messages unless @content
|
551
|
+
raise TypeError, err_msg+"'Message'" if @content != :messages
|
552
|
+
elsif obj.is_a? MsgGroup
|
553
|
+
@content = :groups unless @content
|
554
|
+
raise TypeError, err_msg+"'MsgGroup'" if @content != :groups
|
555
|
+
else
|
556
|
+
raise TypeError, "Only Message or MsgGroup allowed here"
|
557
|
+
end
|
558
|
+
obj.validate if auto_validate
|
559
|
+
super( obj )
|
560
|
+
end
|
561
|
+
|
562
|
+
end
|
563
|
+
|
564
|
+
|
565
|
+
#########################################################################
|
566
|
+
#
|
567
|
+
# A "MsgGroup" is a special "Collection with header and trailer"
|
568
|
+
# It collects "Message" objects and is only rarely used.
|
569
|
+
|
570
|
+
|
571
|
+
class MsgGroup < Collection_HT
|
572
|
+
|
573
|
+
def initialize(p, user_par = nil)
|
574
|
+
super(p, p.root, 'MsgGroup')
|
575
|
+
# ...
|
576
|
+
end
|
577
|
+
|
578
|
+
|
579
|
+
def fmt_of_DE(id) # :nodoc:
|
580
|
+
@parent.fmt_of_DE(id)
|
581
|
+
end
|
582
|
+
|
583
|
+
|
584
|
+
def each_BCDS(id, &b) # :nodoc:
|
585
|
+
@parent.each_BCDS(id, &b)
|
586
|
+
end
|
587
|
+
|
588
|
+
# Add only Message objects to a message group!
|
589
|
+
|
590
|
+
def add (msg)
|
591
|
+
raise "Only Messages allowed here" unless msg.is_a? Message
|
592
|
+
super
|
593
|
+
end
|
594
|
+
|
595
|
+
end
|
596
|
+
|
597
|
+
|
598
|
+
#########################################################################
|
599
|
+
#
|
600
|
+
# A "Message" is a special "Collection with header and trailer"
|
601
|
+
# It collects "Segment" objects.
|
602
|
+
|
603
|
+
class Message < Collection_HT
|
604
|
+
|
605
|
+
# @@msgCounter = 1
|
606
|
+
|
607
|
+
def initialize( p, user_par=nil )
|
608
|
+
super(p, p.root, 'Message')
|
609
|
+
end
|
610
|
+
|
611
|
+
|
612
|
+
def fmt_of_DE(id) # :nodoc:
|
613
|
+
de = @maindata.de(id)
|
614
|
+
return @parent.fmt_of_DE(id) if de.nil?
|
615
|
+
de.format
|
616
|
+
end
|
617
|
+
|
618
|
+
|
619
|
+
def each_BCDS(id, &b) # :nodoc:
|
620
|
+
begin
|
621
|
+
@maindata.each_BCDS(id, &b )
|
622
|
+
rescue EDILookupError
|
623
|
+
@parent.each_BCDS(id, &b)
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
|
628
|
+
# Add only Segment objects to a message!
|
629
|
+
|
630
|
+
def add (seg)
|
631
|
+
raise "Only Segments allowed here" unless seg.is_a? Segment
|
632
|
+
super
|
633
|
+
end
|
634
|
+
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
#########################################################################
|
639
|
+
#
|
640
|
+
# A "Segment" is a special Collection of type "S" (segment-like),
|
641
|
+
# similar to "CDE". Special Segment attributes are:
|
642
|
+
# +sg_name+:: The name of its segment group (optional)
|
643
|
+
# +level+:: The segment's hierarchy level, an integer
|
644
|
+
|
645
|
+
|
646
|
+
class Segment < Collection_S
|
647
|
+
|
648
|
+
attr_reader :sg_name, :level
|
649
|
+
|
650
|
+
# Returns true if segment is a TNode (i.e. a trigger segment).
|
651
|
+
# Note that only TNodes may have descendants.
|
652
|
+
|
653
|
+
def is_tnode?
|
654
|
+
@tnode
|
655
|
+
end
|
656
|
+
|
657
|
+
# Returns array of all segments that have the current segment
|
658
|
+
# as ancestor.
|
659
|
+
|
660
|
+
def descendants
|
661
|
+
self['descendant::*']
|
662
|
+
end
|
663
|
+
|
664
|
+
# Returns array of all segments with the current segment
|
665
|
+
# as ancestor, including the current segment.
|
666
|
+
# For trigger segments, this method returns all segments
|
667
|
+
# of one instance of the corresponding segment group.
|
668
|
+
|
669
|
+
def descendants_and_self
|
670
|
+
self['descendant-or-self::*']
|
671
|
+
end
|
672
|
+
|
673
|
+
# Returns all child elements of the current segment.
|
674
|
+
|
675
|
+
def children
|
676
|
+
self['child::*']
|
677
|
+
end
|
678
|
+
|
679
|
+
# Returns the current segment and all of its child elements.
|
680
|
+
# Useful e.g. to deal with one instance of a segment group
|
681
|
+
# without traversing included segment groups.
|
682
|
+
|
683
|
+
def children_and_self
|
684
|
+
self['child-or-self::*']
|
685
|
+
end
|
686
|
+
|
687
|
+
|
688
|
+
# Access by XPath expression (support is very limited currently)
|
689
|
+
# or by name of the dependent component. Pass them as strings.
|
690
|
+
#
|
691
|
+
# Used internally - try to avoid at user level!
|
692
|
+
# Currently supported XPath expressions:
|
693
|
+
#
|
694
|
+
# - descendant::*
|
695
|
+
# - descendant-or-self::*
|
696
|
+
# - child::*
|
697
|
+
# - child-or-self::*
|
698
|
+
|
699
|
+
def []( xpath_expr )
|
700
|
+
return super( xpath_expr ) if xpath_expr.is_a? Integer
|
701
|
+
|
702
|
+
msg_unsupported = "Unsupported XPath expression: #{xpath_expr}"
|
703
|
+
|
704
|
+
case xpath_expr
|
705
|
+
|
706
|
+
when /\A(descendant|child)(-or-self)?::(.*)/
|
707
|
+
return xpath_matches($1, $2, $3, msg_unsupported)
|
708
|
+
|
709
|
+
# Currently no real path, no predicate available supported
|
710
|
+
when /\//, /\[/, /\]/
|
711
|
+
raise IndexError, msg_unsupported
|
712
|
+
|
713
|
+
when /child::(\w+)/ # ignore & accept default axis "child"
|
714
|
+
return super( $1 )
|
715
|
+
|
716
|
+
when /::/ # No other axes supported for now
|
717
|
+
raise IndexError, msg_unsupported
|
718
|
+
|
719
|
+
else # assume simple element name
|
720
|
+
return super( xpath_expr )
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
|
725
|
+
def inspect( indent='', symlist=[] )
|
726
|
+
symlist += [:sg_name, :level]
|
727
|
+
super
|
728
|
+
end
|
729
|
+
|
730
|
+
|
731
|
+
# Update attributes with information from a corresponding node instance
|
732
|
+
|
733
|
+
def update_with( ni ) # :nodoc:
|
734
|
+
return nil if ni.name != @name # Names must match; consider a raise!
|
735
|
+
@status, @maxrep, @sg_name, @rep, @index, @level, @tnode = ni.status,\
|
736
|
+
ni.maxrep, ni.sg_name, ni.inst_cnt, ni.index, ni.level, ni.is_tnode?
|
737
|
+
self
|
738
|
+
end
|
739
|
+
|
740
|
+
private
|
741
|
+
|
742
|
+
def add (obj)
|
743
|
+
raise "Only DE or CDE allowed here" unless obj.is_a? DE or obj.is_a? CDE
|
744
|
+
super( obj )
|
745
|
+
end
|
746
|
+
|
747
|
+
def xpath_matches( axis, or_self, element, msg_unsupported )
|
748
|
+
raise IndexError, msg_unsupported if element != '*'
|
749
|
+
results = []
|
750
|
+
results << self if or_self
|
751
|
+
child_mode = (axis=='child')
|
752
|
+
return results unless self.is_tnode?
|
753
|
+
|
754
|
+
# Now add all segments in self's "tail"
|
755
|
+
msg = parent
|
756
|
+
index = msg.index(self)
|
757
|
+
raise IndexError, "#{name} not found in own message?!" unless index
|
758
|
+
loop do
|
759
|
+
index += 1
|
760
|
+
seg = msg[index]
|
761
|
+
next if child_mode and seg.level > level+1 # other descendants
|
762
|
+
break if seg.level <= level
|
763
|
+
results << seg
|
764
|
+
end
|
765
|
+
results
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
|
770
|
+
#########################################################################
|
771
|
+
#
|
772
|
+
# Composite data element, primarily used in the EDIFACT context.
|
773
|
+
# A "CDE" is a special Collection of type "S" (segment-like),
|
774
|
+
# similar to "Segment".
|
775
|
+
#
|
776
|
+
class CDE < Collection_S
|
777
|
+
|
778
|
+
private
|
779
|
+
|
780
|
+
# Add a DE
|
781
|
+
|
782
|
+
def add( obj )
|
783
|
+
raise "Only DE allowed here" unless obj.is_a? DE
|
784
|
+
super
|
785
|
+
end
|
786
|
+
|
787
|
+
end
|
788
|
+
|
789
|
+
#########################################################################
|
790
|
+
#
|
791
|
+
# A basic data element. Its content is accessible through methods +value+
|
792
|
+
# and <tt>value=</tt>. Allowed contents is described by attribute +format+.
|
793
|
+
#
|
794
|
+
# Note that values are usually Strings, or Numerics when the format indicates
|
795
|
+
# a numeric value. Other objects are conceivable, as long as they
|
796
|
+
# maintain a reasonable +to_s+ for their representation.
|
797
|
+
#
|
798
|
+
# The external representation of the (abstract) value may further depend on
|
799
|
+
# rules of the unterlying EDI standard. E.g., UN/EDIFACT comes with a set
|
800
|
+
# of reserved characters and an escaping mechanism.
|
801
|
+
|
802
|
+
class DE < EDI::Object
|
803
|
+
attr_accessor :value
|
804
|
+
attr_reader :format, :status
|
805
|
+
|
806
|
+
def initialize( p, name, status, fmt )
|
807
|
+
@parent, @root, @name, @format, @status = p, p.root, name, fmt, status
|
808
|
+
raise "#{location}: 'nil' is not an allowed format." if fmt.nil?
|
809
|
+
raise "#{location}: 'nil' is not an allowed status." if status.nil?
|
810
|
+
@value = nil
|
811
|
+
end
|
812
|
+
|
813
|
+
|
814
|
+
def to_s
|
815
|
+
str = self.value
|
816
|
+
return str if str.is_a? String
|
817
|
+
str = str.to_s; len = str.length
|
818
|
+
return str unless format =~ /n(\d+)/ && len != (fixlen=$1.to_i)
|
819
|
+
raise "#{location}: Too long (#{l}) for fmt #{format}" if len > fixlen
|
820
|
+
'0' * (fixlen - len) + str
|
821
|
+
end
|
822
|
+
|
823
|
+
|
824
|
+
def inspect( indent='' )
|
825
|
+
indent + self.name+': ' + [:value, :format, :status].map do |sym|
|
826
|
+
"#{sym} = #{(s=send(sym)).nil? ? 'nil' : s.to_s}"
|
827
|
+
end.join(', ') + "\n"
|
828
|
+
end
|
829
|
+
|
830
|
+
|
831
|
+
# Performs various validation checks and returns the number of
|
832
|
+
# issues found (plus the value of +err_count+):
|
833
|
+
#
|
834
|
+
# - empty while mandatory?
|
835
|
+
# - character set limitations violated?
|
836
|
+
# - various format restrictions violated?
|
837
|
+
|
838
|
+
def validate( err_count=0 )
|
839
|
+
location = "DE #{parent.name}/#{@name}"
|
840
|
+
if empty?
|
841
|
+
if required?
|
842
|
+
warn "#{location}: Empty though mandatory!"
|
843
|
+
err_count += 1
|
844
|
+
end
|
845
|
+
else
|
846
|
+
#
|
847
|
+
# Charset check
|
848
|
+
#
|
849
|
+
if (pos = (value =~ root.illegal_charset_pattern))# != nil
|
850
|
+
warn "#{location}: Illegal character: #{value[pos].chr}"
|
851
|
+
err_count += 1
|
852
|
+
end
|
853
|
+
#
|
854
|
+
# Format check, raise error if not consistent!
|
855
|
+
#
|
856
|
+
if @format =~ /^(a|n|an)(..)?(\d+)$/
|
857
|
+
_a_n_an, _upto, _size = $1, $2, $3
|
858
|
+
case _a_n_an
|
859
|
+
|
860
|
+
when 'n'
|
861
|
+
strval = value.to_s
|
862
|
+
re = Regexp.new('^(-)?(\d+)([.,]\d+)?$')
|
863
|
+
md = re.match strval
|
864
|
+
if md.nil?
|
865
|
+
raise "#{location}: '#{strval}' - not matching format #@format"
|
866
|
+
# warn "#{strval} - not matching format #@format"
|
867
|
+
# err_count += 1
|
868
|
+
end
|
869
|
+
|
870
|
+
len = strval.length
|
871
|
+
# Sign char does not go into length count:
|
872
|
+
len -= 1 if md[1]=='-'
|
873
|
+
# Decimal char does not go into length count:
|
874
|
+
len -= 1 if not md[3].nil?
|
875
|
+
# len -= 1 if (md[1]=='-' and md[3]) || (md[1] != '' and not md[3])
|
876
|
+
break if not required? and len == 0
|
877
|
+
if len > _size.to_i
|
878
|
+
# if _upto.nil? and len != _size.to_i or len > _size.to_i
|
879
|
+
warn "Context in #{location}: #{_a_n_an}, #{_upto}, #{_size}; #{md[1]}, #{md[2]}, #{md[3]}"
|
880
|
+
warn "Length # mismatch in #{location}: #{len} vs. #{_size}"
|
881
|
+
err_count += 1
|
882
|
+
# warn " (strval was: '#{strval}')"
|
883
|
+
end
|
884
|
+
if md[1] =~/^0+/
|
885
|
+
warn "#{strval} contains leading zeroes"
|
886
|
+
err_count += 1
|
887
|
+
end
|
888
|
+
if md[3] and md[3]=~ /.0+$/
|
889
|
+
warn "#{strval} contains trailing decimal sign/zeroes"
|
890
|
+
err_count += 1
|
891
|
+
end
|
892
|
+
|
893
|
+
when 'a', 'an'
|
894
|
+
# len = value.is_a?(Numeric) ? value.to_s.length : value.length
|
895
|
+
len = value.to_s.length
|
896
|
+
if _upto.nil? and len != $3.to_i or len > $3.to_i
|
897
|
+
warn "Length mismatch in #{location}: #{len} vs. #{_size}"
|
898
|
+
err_count += 1
|
899
|
+
end
|
900
|
+
else
|
901
|
+
raise "#{location}: Illegal format prefix #{_a_n_an}"
|
902
|
+
# err_count += 1
|
903
|
+
end
|
904
|
+
|
905
|
+
else
|
906
|
+
warn "#{location}: Illegal format: #{@format}!"
|
907
|
+
err_count += 1
|
908
|
+
end
|
909
|
+
end
|
910
|
+
err_count
|
911
|
+
end
|
912
|
+
|
913
|
+
|
914
|
+
# Returns +true+ if value is not +nil+.
|
915
|
+
# Note that assigning an empty string to a DE makes it "not empty".
|
916
|
+
def empty?
|
917
|
+
@value == nil
|
918
|
+
end
|
919
|
+
|
920
|
+
|
921
|
+
# Returns +true+ if this is a required / mandatory segment.
|
922
|
+
def required?
|
923
|
+
@status == 'M' or @status == 'R'
|
924
|
+
end
|
925
|
+
|
926
|
+
end
|
927
|
+
|
928
|
+
end # module EDI
|