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