blib/lib/Business/EDI.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 438 | 570 | 76.8 |
branch | 214 | 440 | 48.6 |
condition | 35 | 95 | 36.8 |
subroutine | 46 | 54 | 85.1 |
pod | 7 | 22 | 31.8 |
total | 740 | 1181 | 62.6 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Business::EDI; | ||||||
2 | |||||||
3 | 15 | 15 | 835618 | use strict; | |||
15 | 40 | ||||||
15 | 693 | ||||||
4 | 15 | 15 | 93 | use warnings; | |||
15 | 31 | ||||||
15 | 444 | ||||||
5 | 15 | 15 | 89 | use Carp; | |||
15 | 32 | ||||||
15 | 1754 | ||||||
6 | # use Data::Dumper; | ||||||
7 | |||||||
8 | our $VERSION = 0.05; | ||||||
9 | |||||||
10 | 15 | 15 | 16812 | use UNIVERSAL::require; | |||
15 | 26937 | ||||||
15 | 145 | ||||||
11 | 15 | 15 | 75082 | use Data::Dumper; | |||
15 | 23259 | ||||||
15 | 1087 | ||||||
12 | 15 | 15 | 91 | use File::Spec; | |||
15 | 33 | ||||||
15 | 321 | ||||||
13 | 15 | 15 | 119431 | use CGI qw//; | |||
15 | 342505 | ||||||
15 | 545 | ||||||
14 | 15 | 15 | 13497 | use Business::EDI::CodeList; | |||
15 | 62 | ||||||
15 | 229 | ||||||
15 | 15 | 15 | 19779 | use Business::EDI::Composite; | |||
15 | 82 | ||||||
15 | 741 | ||||||
16 | 15 | 15 | 25084 | use Business::EDI::DataElement; | |||
15 | 52 | ||||||
15 | 226 | ||||||
17 | 15 | 15 | 12154 | use Business::EDI::Segment; | |||
15 | 38 | ||||||
15 | 132 | ||||||
18 | 15 | 15 | 12526 | use Business::EDI::Spec; | |||
15 | 57 | ||||||
15 | 318 | ||||||
19 | |||||||
20 | our $debug = 0; | ||||||
21 | our %debug = (); | ||||||
22 | our $error; # for the whole class | ||||||
23 | my %fields = (); | ||||||
24 | |||||||
25 | our $AUTOLOAD; | ||||||
26 | 0 | 0 | 0 | sub DESTROY {} # | |||
27 | sub AUTOLOAD { | ||||||
28 | 30909 | 30909 | 96751 | my $self = shift; | |||
29 | 30909 | 50 | 71776 | my $class = ref($self) or croak "AUTOLOAD error: $self is not an object, looking for $AUTOLOAD"; | |||
30 | 30909 | 47584 | my $name = $AUTOLOAD; | ||||
31 | |||||||
32 | 30909 | 112151 | $name =~ s/.*://; # strip leading package stuff | ||||
33 | 30909 | 100 | 66 | 221392 | $name =~ /^syntax/ or # leave syntax, | ||
100 | |||||||
34 | $name =~ /^SG\d+$/ or # leave SGxx alone (for segment groups) | ||||||
35 | $name =~ s/^s(eg(ment)?)?//i or # strip segment (a prefix to avoid numerical method names) | ||||||
36 | $name =~ s/^p(art)?//i; # strip part -- autoload's parallel accessor, e.g. ->part4343 to ->part(4343) | ||||||
37 | |||||||
38 | 30909 | 50 | 76504 | $debug and warn "AUTOLOADING '$name' for " . $class; | |||
39 | |||||||
40 | 30909 | 100 | 86213 | if (exists $self->{_permitted}->{$name}) { # explicitly named accessible fields | |||
41 | 30792 | 50 | 55515 | if (@_) { | |||
42 | 0 | 0 | return $self->{$name} = shift; | ||||
43 | } else { | ||||||
44 | 30792 | 165715 | return $self->{$name}; | ||||
45 | } | ||||||
46 | } | ||||||
47 | |||||||
48 | 117 | 100 | 386 | if (ref $self->{def} eq 'ARRAY') { # spec defined subelements | |||
49 | 1 | 50 | 10 | if ($name =~ s/^all_(.+)$/$1/i) { | |||
50 | 1 | 50 | 5 | @_ and croak "AUTOLOAD error: all_$name is read_only, rec'd argument(s): " . join(', ', @_); | |||
51 | 1 | 50 | 3 | if ($debug) { | |||
52 | 0 | 0 | warn "AUTOLOADing " . $self->{code} . "/all_$name (from " . scalar(@{$self->{array}}) . " arrayed elements): " | ||||
0 | 0 | ||||||
53 | 0 | 0 | . join(' ', map {$_->{code}} @{$self->{array}}); | ||||
0 | 0 | ||||||
54 | 0 | 0 | 0 | $debug > 1 and print STDERR Dumper($self), "\n"; | |||
55 | } | ||||||
56 | 1 | 50 | 10 | my $target = $name =~ /^SG\d+$/ ? ($self->{code} . "/$name") : $name; | |||
57 | 1 | 50 | 2 | return grep {$_->{code} and $_->{code} eq $target} @{$self->{array}}; # return array | |||
29 | 168 | ||||||
1 | 4 | ||||||
58 | } | ||||||
59 | 0 | 0 | return __PACKAGE__->_deepload_array($self, $name, @_); # not $self->_deepload - avoid recursion | ||||
60 | } | ||||||
61 | # lastly, try to reach through any Cxxx Composites, if the target is unique | ||||||
62 | 116 | 617 | return __PACKAGE__->_deepload($self, $name, @_); # not $self->_deepload - avoid recursion | ||||
63 | } | ||||||
64 | |||||||
65 | sub _deepload_array { | ||||||
66 | 670 | 670 | 752 | my $pkg = shift; # does nothing | |||
67 | 670 | 50 | 1229 | my $self = shift or return; | |||
68 | 670 | 50 | 1237 | my $name = shift or return; | |||
69 | 670 | 50 | 1323 | unless ($self->{def}) { | |||
70 | 0 | 0 | die "_deepload_array of '$name' attempted on an object that does not have a spec definition"; | ||||
71 | 0 | 0 | return; | ||||
72 | } | ||||||
73 | |||||||
74 | 670 | 605 | my @hits = grep {$_->{code} eq $name} @{$self->{def}}; | ||||
10012 | 16191 | ||||||
670 | 1361 | ||||||
75 | 670 | 815 | my $defcount = scalar @{$self->{def}}; | ||||
670 | 1131 | ||||||
76 | 670 | 769 | my $hitcount = scalar @hits; | ||||
77 | 670 | 726 | my $total_possible = 0; | ||||
78 | 670 | 984 | foreach (@hits) { | ||||
79 | 670 | 50 | 1954 | $total_possible += ($_->{repeats} || 1); | |||
80 | } | ||||||
81 | 670 | 100 | 2131 | $name =~ /^SG\d+$/ and $name = $self->{message_code} . "/$name"; # adjust key for SGs | |||
82 | 670 | 50 | 1227 | $debug and warn "Looking for '$name' matches $hitcount of $defcount subelements, w/ $total_possible instances: " . join(' ', map {$_->{code}} @hits); | |||
0 | 0 | ||||||
83 | 670 | 50 | 1322 | $debug and warn ref($self) . " self->{array} has " . scalar(@{$self->{array}}) . " elements of data"; | |||
0 | 0 | ||||||
84 | |||||||
85 | # Logic: | ||||||
86 | # If there is only one possible element to match, then we can read/write to it. | ||||||
87 | # But if there are multiple repetitions possible, then we cannot tell which one to target, | ||||||
88 | # UNLESS it is a read operation and there is only one such element populated. | ||||||
89 | # Write operation still would be indifferentiable between new element constructor and existing elememt overwrite. | ||||||
90 | 670 | 50 | 33 | 2270 | if ($total_possible == 1 or ($hitcount == 1 and not @_)) { | ||
0 | 66 | ||||||
91 | 670 | 764 | foreach (@{$self->{array}}) { | ||||
670 | 1781 | ||||||
92 | 2558 | 100 | 5346 | $_->code eq $name or next; | |||
93 | 670 | 50 | 1145 | if (@_) { | |||
94 | 0 | 0 | return $_ = shift; | ||||
95 | } else { | ||||||
96 | 670 | 3013 | return $_; | ||||
97 | } | ||||||
98 | } | ||||||
99 | # if we got here, it's a valid target w/ no populated value (no code match) | ||||||
100 | 0 | 0 | return; | ||||
101 | # @_ or return $self->_subelement_helper($name, {}, $self->{message_code}); # so you get an empty object of the correct type on read | ||||||
102 | # TODO: for 1-hit write, splice in at the correct position. Tricky. | ||||||
103 | } elsif ($total_possible == 0) { | ||||||
104 | 0 | 0 | 0 | 0 | $debug and $debug > 1 and print STDERR "FAILED _deepload_array of '$name' in object: ", Dumper($self); | ||
105 | } | ||||||
106 | 0 | 0 | 0 | croak "AUTOLOAD error: Cannot " . (@_ ? 'write' : 'read') . " '$name' field of class '" . ref($self) | |||
107 | . "', $hitcount matches ($total_possible repetitions) in subelements"; | ||||||
108 | } | ||||||
109 | |||||||
110 | sub _deepload { | ||||||
111 | 1172 | 1172 | 1921 | my $pkg = shift; # does nothing | |||
112 | 1172 | 50 | 11951 | my $self = shift or return; | |||
113 | 1172 | 50 | 2273 | my $name = shift or return; | |||
114 | 1172 | 50 | 2440 | $self->{_permitted} or return; | |||
115 | |||||||
116 | 1172 | 3546 | my @partkeys = $self->part_keys; | ||||
117 | 1172 | 2060 | my @keys = grep {/^C\d{3}$/} @partkeys; | ||||
3546 | 10591 | ||||||
118 | 1172 | 1436 | my $allcount = scalar(@partkeys); | ||||
119 | 1172 | 1479 | my $ccount = scalar(@keys); | ||||
120 | 1172 | 50 | 2276 | $debug and warn "Looking for $name under $allcount subelements, $ccount Composites: " . join(' ', @keys); | |||
121 | |||||||
122 | 1172 | 1767 | my @hits = grep {$name eq $_} @partkeys; | ||||
3546 | 8293 | ||||||
123 | 1172 | 50 | 2880 | if (scalar @hits) { | |||
50 | |||||||
124 | |||||||
125 | } elsif ($ccount) { | ||||||
126 | 1172 | 50 | 2843 | my $spec = $self->spec or croak "You must set a spec version (via constructor or spec method) before EDI can autoload objects"; | |||
127 | 1172 | 3505 | my $part = $spec->get_spec('composite'); | ||||
128 | 1172 | 2783 | foreach my $code (@keys) { | ||||
129 | 1182 | 50 | 2539 | $part->{$code} or croak(ref($self) . " Object _permitted composite code '$code' not found in spec version " . $spec->version); | |||
130 | 1182 | 1445 | my @subparts = grep {$_->{code} eq $name} @{$part->{$code}->{parts}}; | ||||
3806 | 8809 | ||||||
1182 | 2438 | ||||||
131 | 1182 | 100 | 2802 | @subparts and push(@hits, map {$code} @subparts); | |||
1172 | 4466 | ||||||
132 | # important here, we add the Cxxx code once per hit in its subparts. Multiple hits means we cannot target cleanly. | ||||||
133 | } | ||||||
134 | } | ||||||
135 | 1172 | 1728 | my $hitcount = scalar(@hits); | ||||
136 | 1172 | 50 | 2204 | $debug and warn "Found $name has $hitcount possible match(es) in $ccount Composites: " . join(' ', @hits); | |||
137 | 1172 | 50 | 2020 | if ($hitcount == 1) { | |||
0 | |||||||
138 | 1172 | 50 | 1950 | if (@_) { | |||
139 | 0 | 0 | return $self->{$hits[0]}->{$name} = shift; | ||||
140 | } else { | ||||||
141 | 1172 | 8445 | return $self->{$hits[0]}->{$name}; | ||||
142 | } | ||||||
143 | } elsif ($hitcount > 1) { | ||||||
144 | 0 | 0 | croak "AUTOLOAD error: Cannot access '$name' field of class '" . ref($self) . "', " | ||||
145 | . " $hitcount indeterminate matches in collapsable subelements"; | ||||||
146 | } | ||||||
147 | # else hitcount == 0 | ||||||
148 | 0 | 0 | 0 | 0 | $debug and $debug > 1 and print STDERR "FAILED _deepload of '$name' in object: ", Dumper($self); | ||
149 | 0 | 0 | croak "AUTOLOAD error: Cannot access '$name' field of class '" . ref($self) | ||||
150 | . "' (or $allcount collapsable subelements, $ccount Composites)"; | ||||||
151 | } | ||||||
152 | |||||||
153 | # Constructors | ||||||
154 | |||||||
155 | sub new { | ||||||
156 | 55 | 55 | 1 | 2057 | my $class = shift; | ||
157 | 55 | 248 | my %args; | ||||
158 | 55 | 100 | 380 | if (scalar @_ eq 1) { | |||
100 | |||||||
159 | 8 | 29 | $args{version} = shift; | ||||
160 | } elsif (@_) { | ||||||
161 | 46 | 50 | 232 | scalar(@_) % 2 and croak "Odd number of arguments to new() incorrect. Use (name1 => value1) style."; | |||
162 | 46 | 195 | %args = @_; | ||||
163 | } | ||||||
164 | 55 | 321 | my $stuff = {_permitted => {(map {$_ => 1} keys %fields)}, %fields}; | ||||
0 | 0 | ||||||
165 | 55 | 226 | foreach (keys %args) { | ||||
166 | 54 | 50 | 353 | $_ eq 'version' and next; # special case | |||
167 | 0 | 0 | 0 | exists ($stuff->{_permitted}->{$_}) or croak "Unrecognized argument to new: $_ => $args{$_}"; | |||
168 | } | ||||||
169 | 55 | 202 | my $self = bless($stuff, $class); | ||||
170 | 55 | 100 | 201 | if ($args{version}) { | |||
171 | 54 | 50 | 305 | $self->spec(version => $args{version}) or croak "Unrecognized spec version '$args{version}'"; | |||
172 | } | ||||||
173 | 55 | 50 | 33 | 283 | $debug and $debug > 1 and print Dumper($self); | ||
174 | 55 | 435 | return $self; | ||||
175 | } | ||||||
176 | |||||||
177 | # BIG Complicated META-Constructors!! | ||||||
178 | |||||||
179 | sub _common_constructor { | ||||||
180 | 1843 | 1843 | 2803 | my $self = shift; | |||
181 | 1843 | 50 | 4166 | my $type = shift or die "Internal error: _common_constructor called without required argument for object type"; | |||
182 | 1843 | 50 | 4350 | my $spec = $self->spec or croak "You must set a spec version (via constructor or spec method) before EDI can create $type objects"; | |||
183 | 1843 | 7833 | my $part = $spec->get_spec($type); | ||||
184 | 1843 | 50 | 6209 | my $code = uc(shift) or croak "No $type code specified"; | |||
185 | 1843 | 2596 | my $body = shift; | ||||
186 | |||||||
187 | 1843 | 50 | 6036 | $part->{$code} or return $self->carp_error("$type code '$code' is not found amongst " | |||
188 | . scalar(keys %$part) ." ". $type . "s in spec version " . $spec->version); # . ": " . Dumper([sort keys %$part])); | ||||||
189 | |||||||
190 | 1843 | 50 | 6032 | unless (ref($body) eq 'HASH') { | |||
191 | 0 | 0 | return $self->carp_error("body argument for $type must be HASHREF, not '" . ref($body) . "'"); | ||||
192 | } | ||||||
193 | 1843 | 2280 | my @subparts = map {$_->{code}} @{$part->{$code}->{parts}}; | ||||
4948 | 15288 | ||||||
1843 | 5853 | ||||||
194 | 1843 | 3222 | my @required = map {$_->{code}} grep {$_->{mandatory}} @{$part->{$code}->{parts}}; | ||||
1264 | 3676 | ||||||
4948 | 9225 | ||||||
1843 | 4223 | ||||||
195 | |||||||
196 | 1843 | 2481 | my ($compspec, @compcodes); | ||||
197 | 0 | 0 | my ( $segspec, @seggroups); | ||||
198 | 1843 | 3526 | foreach (@subparts) { | ||||
199 | 4948 | 50 | 33 | 12568 | /^SG\d+$/ and push(@seggroups, $_) and next; | ||
200 | 4948 | 100 | 66 | 32679 | /^C\d{3}$/ and push(@compcodes, $_) and next; | ||
201 | } | ||||||
202 | 1843 | 100 | 9788 | $compspec = $spec->get_spec('composite') if @compcodes; | |||
203 | # $segspec = $spec->get_spec('segment') if @seggroups; | ||||||
204 | |||||||
205 | 1843 | 3722 | my $normal; | ||||
206 | # Now we normalize the body according to the spec (apply wrappers) | ||||||
207 | 1843 | 5847 | foreach my $key (keys %$body) { | ||||
208 | 2952 | 100 | 4684 | if (grep {$key eq $_} @subparts) { | |||
10590 | 50 | 22287 | |||||
209 | 2940 | 7997 | $normal->{$key} = $body->{$key}; # simple case | ||||
210 | 2940 | 6976 | next; | ||||
211 | } | ||||||
212 | elsif (@compcodes) { | ||||||
213 | 12 | 27 | my @hits; | ||||
214 | 12 | 32 | foreach my $compcode (@compcodes) { | ||||
215 | 24 | 42 | push @hits, map {$compcode} grep {$_->{code} eq $key} @{$compspec->{$compcode}->{parts}}; | ||||
12 | 50 | ||||||
84 | 233 | ||||||
24 | 89 | ||||||
216 | } | ||||||
217 | 12 | 50 | 58 | if (scalar(@hits) == 1) { | |||
0 | |||||||
218 | 12 | 59 | $normal->{$hits[0]}->{$key} = $body->{$key}; # only one place for it to go, so apply the wrapper | ||||
219 | 12 | 38 | next; | ||||
220 | } elsif (scalar(@hits) > 1) { | ||||||
221 | 0 | 0 | return $self->carp_error("$type subpart '$key' has " . scalar(@hits) | ||||
222 | . " indeterminate matches under composites: " . join(', ', @hits) | ||||||
223 | ); | ||||||
224 | } | ||||||
225 | 0 | 0 | return $self->carp_error("$type subpart '$key' not found in spec " . $spec->version); | ||||
226 | } | ||||||
227 | } | ||||||
228 | |||||||
229 | 1843 | 50 | 5075 | $debug and printf STDERR "creating $type/$code with %d spec subpart(s): %s\n", scalar(@subparts), join(' ', @subparts); | |||
230 | # push @subparts, 'debug'; | ||||||
231 | 1843 | 6250 | my $unblessed = $self->unblessed($normal, \@subparts); | ||||
232 | 1843 | 50 | 4256 | $unblessed or return; | |||
233 | 1843 | 7490 | my $new = bless($unblessed, __PACKAGE__ . '::' . ucfirst($type)); | ||||
234 | 1843 | 4554 | $new->spec($spec); | ||||
235 | 1843 | 3881 | $new->{_permitted}->{code} = 1; | ||||
236 | 1843 | 3580 | $new->{_permitted}->{label} = 1; | ||||
237 | 1843 | 3654 | $new->{code} = $code; | ||||
238 | 1843 | 5201 | $new->{label} = $part->{$code}->{label}; | ||||
239 | # $new->debug($debug{$type}) if $debug{$type}; | ||||||
240 | 1843 | 3474 | foreach (@required) { | ||||
241 | 1264 | 50 | 3403 | unless (defined $new->part($_)) { | |||
242 | 0 | 0 | return $self->carp_error("Required field $type/$code/$_ not populated"); | ||||
243 | } | ||||||
244 | } | ||||||
245 | 1843 | 12527 | return $new; | ||||
246 | } | ||||||
247 | |||||||
248 | sub _def_based_constructor { | ||||||
249 | 864 | 864 | 1693 | my $self = shift; | |||
250 | 864 | 50 | 2359 | my $type = shift or die "Internal error: _def_based_constructor called without required argument for object type"; | |||
251 | 864 | 50 | 2282 | my $spec = $self->spec or croak "You must set a spec version (via constructor or spec method) before EDI can create $type objects"; | |||
252 | 864 | 2872 | my $page = $self->spec_page($type); # page of the spec | ||||
253 | 864 | 50 | 3624 | my $code = uc(shift) or croak "No $type code specified"; | |||
254 | 864 | 1567 | my $body = shift; | ||||
255 | 864 | 100 | 66 | 4964 | my $message_code = (@_ and $_[0]) ? shift : ''; | ||
256 | 864 | 1403 | my $page_code; | ||||
257 | |||||||
258 | 864 | 100 | 3585 | if ($type eq 'message') { | |||
50 | |||||||
259 | 19 | 39 | $message_code = $code; | ||||
260 | 19 | 45 | $page_code = $code; | ||||
261 | } elsif ($type eq 'segment_group') { | ||||||
262 | 845 | 50 | 33 | 3496 | $code =~ /^SG\d+$/ and $message_code and $code = "$message_code/$code"; | ||
263 | 845 | 50 | 7712 | $code =~ /^(\S+)\/(SG\d+)$/ or return $self->carp_error("Cannot spec $type '$code' without message. Use xpath style, like 'ORDERS/SG27'"); | |||
264 | 845 | 50 | 3126 | $page = $page->{$1} or return $self->carp_error("Message $1 does not have any " . $type . "s in spec version " . $spec->version); | |||
265 | 845 | 1518 | $message_code = $1; | ||||
266 | 845 | 1423 | $page_code = $2; | ||||
267 | # tighen spec down past message level based on first part of key | ||||||
268 | } | ||||||
269 | |||||||
270 | 864 | 50 | 3399 | unless (ref($body) eq 'ARRAY') { | |||
271 | 0 | 0 | return $self->carp_error("body argument to $type() must be ARRAYREF, not '" . ref($body) . "'"); | ||||
272 | } | ||||||
273 | |||||||
274 | 864 | 1395 | my @subparts = @{$page->{$page_code}->{parts}}; | ||||
864 | 2235236 | ||||||
275 | 864 | 50 | 2605 | $debug and printf STDERR "creating $type/$code with %d spec subpart(s): %s\n", scalar(@subparts), join(' ', map {$_->{code}} @subparts); | |||
0 | 0 | ||||||
276 | 864 | 50 | 2370 | $debug and print STDERR "calling \$self->unblessed_array(\$body, \$page->{$page_code}->{parts}, '$message_code')\n"; | |||
277 | 864 | 3789 | my $unblessed = $self->unblessed_array($body, \@subparts, $message_code); # doesn't yet support arrayref(?) | ||||
278 | 864 | 50 | 2063 | $unblessed or return; | |||
279 | 864 | 3368 | my $new = bless($unblessed, __PACKAGE__ . '::' . ucfirst($type)); | ||||
280 | 864 | 2289 | $new->spec($spec); | ||||
281 | 864 | 1883 | $new->{_permitted}->{code} = 1; | ||||
282 | 864 | 1805 | $new->{_permitted}->{message_code} = 1; | ||||
283 | 864 | 2374 | $new->{_permitted}->{label} = 1; | ||||
284 | 864 | 82773 | $new->{code} = $code; | ||||
285 | 864 | 1822 | $new->{message_code} = $message_code; # same as code for messages, different for SGs | ||||
286 | 864 | 2949 | $new->{label} = $page->{$page_code}->{label}; | ||||
287 | 864 | 100 | 2153 | if ($type eq 'segment_group') { | |||
288 | 845 | 2881 | $new->{sg_code} = $page_code; | ||||
289 | } | ||||||
290 | 864 | 5290 | return $new; | ||||
291 | } | ||||||
292 | |||||||
293 | # Fundamental constructor calls for different object types | ||||||
294 | # These are here so you can just "use Business::EDI;" and not have to worry about using different | ||||||
295 | # modules for different data objects. | ||||||
296 | |||||||
297 | sub segment { | ||||||
298 | 1843 | 1843 | 1 | 14032 | my $self = shift; | ||
299 | 1843 | 6130 | return $self->_common_constructor('segment', @_); | ||||
300 | } | ||||||
301 | |||||||
302 | sub segment_group { | ||||||
303 | 845 | 845 | 0 | 1246 | my $self = shift; | ||
304 | 845 | 2773 | return $self->_def_based_constructor('segment_group', @_); | ||||
305 | # The difference is that segment_group must deal with repeatable segments, other segment groups, etc. | ||||||
306 | } | ||||||
307 | |||||||
308 | # TODO: rename detect_version one something more clueful | ||||||
309 | # The difference is that message() expects you to have declared an EDI spec version already, whereas detect_version | ||||||
310 | # just looks at the contents of the passed data, attempting to extract the encoded version there. | ||||||
311 | |||||||
312 | sub detect_version { | ||||||
313 | 4 | 4 | 0 | 7528 | my $self = shift; | ||
314 | 4 | 40 | return Business::EDI::Message->new(@_); | ||||
315 | } | ||||||
316 | |||||||
317 | sub message { | ||||||
318 | 19 | 19 | 0 | 57471 | my $self = shift; | ||
319 | # my $msg_code = shift; | ||||||
320 | #print Dumper ($body); | ||||||
321 | 19 | 99 | return $self->_def_based_constructor('message', @_); | ||||
322 | } | ||||||
323 | |||||||
324 | sub dataelement { | ||||||
325 | 0 | 0 | 0 | 0 | my $self = shift; | ||
326 | # Business::EDI::DataElement->require; | ||||||
327 | 0 | 0 | Business::EDI::DataElement->new(@_); | ||||
328 | } | ||||||
329 | |||||||
330 | sub composite { | ||||||
331 | 0 | 0 | 0 | 0 | my $self = shift; | ||
332 | # Business::EDI::DataElement->require; | ||||||
333 | 0 | 0 | Business::EDI::Composite->new(@_); | ||||
334 | } | ||||||
335 | |||||||
336 | sub codelist { | ||||||
337 | 2949 | 2949 | 0 | 4251 | my $self = shift; | ||
338 | # my $spec = $self->spec or croak "You must set a spec version (via constructor or spec method) before EDI can create objects"; | ||||||
339 | # my $part = $spec->get_spec('message'); | ||||||
340 | 2949 | 12172 | Business::EDI::CodeList->new_codelist(@_); | ||||
341 | } | ||||||
342 | |||||||
343 | sub spec_page { | ||||||
344 | 1728 | 1728 | 0 | 2143 | my $self = shift; | ||
345 | 1728 | 50 | 3586 | my $spec = $self->spec or croak "You must set a spec version (via constructor or spec method) before EDI can retrieve part of it"; | |||
346 | 1728 | 50 | 4279 | @_ or return carp_error("Missing argument to spec_page()"); | |||
347 | 1728 | 6412 | return $spec->get_spec(@_); # not $self->get_spec .... sorry | ||||
348 | } | ||||||
349 | |||||||
350 | sub get_spec { | ||||||
351 | 122 | 122 | 0 | 265 | my $self = shift; | ||
352 | 122 | 50 | 386 | @_ or return carp_error("Missing argument to get_spec()"); | |||
353 | 122 | 1161 | return Business::EDI::Spec->new(@_); | ||||
354 | } | ||||||
355 | |||||||
356 | # Accessor get/set methods | ||||||
357 | |||||||
358 | sub code { | ||||||
359 | 2562 | 2562 | 1 | 2915 | my $self = shift; | ||
360 | 2562 | 50 | 4467 | @_ and $self->{code} = shift; | |||
361 | 2562 | 8963 | return $self->{code}; | ||||
362 | } | ||||||
363 | |||||||
364 | sub spec { # spec(code) | ||||||
365 | 8684 | 8684 | 0 | 115942 | my $self = shift; | ||
366 | 8684 | 100 | 19459 | if (@_) { # Arg(s) mean we are constructing | |||
367 | 2887 | 100 | 6518 | ref($self) or return $self->get_spec(@_); # Business::EDI->spec(...) style, class method: simple constructor | |||
368 | 2884 | 100 | 7517 | if (ref($_[0]) eq 'Business::EDI::Spec') { # TODO: use isa or whatever the hip OO style of role-checking is | |||
369 | 2765 | 6383 | $self->{spec} = shift; # We got passed a full spec object, just set | ||||
370 | } else { | ||||||
371 | 119 | 533 | $self->{spec} = $self->get_spec(@_); # otherwise construct and retain | ||||
372 | } | ||||||
373 | } | ||||||
374 | 8681 | 50 | 20926 | ref($self) or croak "Cannot use class method Business::EDI->spec as an accessor (spec is uninstantiated). " . | |||
375 | "Get a spec'd object first like: Business::EDI->new('d87a')->spec, " . | ||||||
376 | "or specify the version you want: Business::EDI->spec('default') or Business::EDI->get_spec('default')"; | ||||||
377 | 8681 | 33630 | return $self->{spec}; | ||||
378 | } | ||||||
379 | |||||||
380 | sub error { | ||||||
381 | 0 | 0 | 0 | 0 | my ($self, $msg, $quiet) = @_; | ||
382 | 0 | 0 | 0 | 0 | $msg or return $self->{error} || $error; # just an accessor | ||
383 | 0 | 0 | 0 | 0 | ($debug or ! $quiet) and carp $msg; | ||
384 | 0 | 0 | return $self->{error} = $msg; | ||||
385 | } | ||||||
386 | |||||||
387 | sub carp_error { | ||||||
388 | 0 | 0 | 0 | 0 | my $obj_or_message = shift; | ||
389 | 0 | 0 | my $msg; | ||||
390 | 0 | 0 | 0 | if (@_) { | |||
391 | 0 | 0 | 0 | $msg = (ref($obj_or_message) || $obj_or_message) . ' - ' . shift; | |||
392 | } else { | ||||||
393 | 0 | 0 | $msg = $obj_or_message; | ||||
394 | } | ||||||
395 | 0 | 0 | 0 | if (ref $obj_or_message) { | |||
396 | # do something? | ||||||
397 | } | ||||||
398 | 0 | 0 | carp $msg; | ||||
399 | 0 | 0 | return; # undef: important! | ||||
400 | } | ||||||
401 | |||||||
402 | # ->unblessed($body, \@codes) | ||||||
403 | |||||||
404 | sub unblessed { # call like Business::EDI->unblessed(\%hash, \@codes); | ||||||
405 | 3816 | 3816 | 0 | 5283 | my $class = shift; | ||
406 | 3816 | 6509 | my $body = shift; | ||||
407 | 3816 | 4131 | my $codesref = shift; | ||||
408 | 3816 | 50 | 7644 | $body or return carp_error "1st required argument to unblessed() is EMPTY"; | |||
409 | 3816 | 50 | 7336 | $codesref or return carp_error "2nd required argument to unblessed() is EMPTY"; | |||
410 | 3816 | 50 | 11274 | unless (ref($body) eq 'HASH') { | |||
411 | 0 | 0 | return carp_error "1st argument to unblessed() must be HASHREF, not '" . ref($body) . "'"; | ||||
412 | } | ||||||
413 | 3816 | 50 | 8857 | unless (ref($codesref) eq 'ARRAY') { | |||
414 | 0 | 0 | return carp_error "2nd argument to unblessed() must be ARRAYREF, not '" . ref($codesref) . "'"; | ||||
415 | } | ||||||
416 | 3816 | 50 | 7433 | $debug and printf STDERR "good: unblessed() got body and definition: %s/%s topnodes/defs\n", scalar(keys %$body), scalar(@$codesref); #, Dumper($body), "\n"; | |||
417 | 3816 | 254868 | my $self = {}; | ||||
418 | 3816 | 15180 | foreach (@$codesref) { | ||||
419 | 12820 | 142665 | $self->{_permitted}->{$_} = 1; | ||||
420 | 12820 | 100 | 33578 | $body->{$_} or next; | |||
421 | 6912 | 33 | 870965 | $self->{$_} = Business::EDI->subelement({$_ => $body->{$_}}) || $body->{$_}; | |||
422 | } | ||||||
423 | 3816 | 11710 | return $self; | ||||
424 | } | ||||||
425 | |||||||
426 | # array based object creation (segment groups) | ||||||
427 | # allows repeatable subobjects | ||||||
428 | # enforces mandatory subobjects | ||||||
429 | sub unblessed_array { # call like Business::EDI->unblessed_array(\@pseudo_hashes, \@code_objects); | ||||||
430 | 864 | 864 | 0 | 1284 | my $class = shift; | ||
431 | 864 | 1623 | my $body = shift; | ||||
432 | 864 | 1214 | my $codesref = shift; | ||||
433 | 864 | 50 | 33 | 4725 | my $msg = (@_ and $_[0]) ? shift : ''; | ||
434 | # my $msg = 'ORDRSP'; | ||||||
435 | 864 | 1351 | my $strict = 0; | ||||
436 | 864 | 50 | 2073 | $body or return carp_error "1st required argument 'x' to unblessed_array(x,y,'$msg') is EMPTY"; | |||
437 | 864 | 50 | 2173 | $codesref or return carp_error "2nd required argument 'y' to unblessed_array(x,y,'$msg') is EMPTY"; | |||
438 | 864 | 50 | 2701 | unless (ref($body) eq 'ARRAY') { | |||
439 | 0 | 0 | return carp_error "1st argument to unblessed_array() must be ARRAYREF, not '" . ref($body) . "'"; | ||||
440 | } | ||||||
441 | 864 | 50 | 2387 | unless (ref($codesref) eq 'ARRAY') { | |||
442 | 0 | 0 | return carp_error "2nd argument to unblessed_array() must be ARRAYREF, not '" . ref($codesref) . "'"; | ||||
443 | } | ||||||
444 | 864 | 50 | 2265 | $debug and printf STDERR "good: unblessed_array() got body and definition: %s/%s topnodes/defs\n", scalar(@$body), scalar(@$codesref); #, Dumper($body), "\n"; | |||
445 | 864 | 231508 | my $self = { | ||||
446 | array => [], # subelements get pushed in here | ||||||
447 | def => $codesref, | ||||||
448 | _permitted => {array => 1, def => 1}, | ||||||
449 | }; | ||||||
450 | |||||||
451 | 864 | 50 | 2853 | my $sg_specs = $class->spec_page('segment_group') or croak "Cannot get Segment Group definitions"; | |||
452 | 864 | 50 | 3129 | my $msg_sg_specs = $sg_specs->{$msg} or croak "ERROR: $msg Segment Groups not defined in spec"; | |||
453 | 864 | 1577 | my $codecount = scalar @$codesref; | ||||
454 | 864 | 1622 | my $j = 0; # index for @$codesref | ||||
455 | 864 | 1264 | my $repeats = 0; | ||||
456 | 864 | 1430 | my $last_matched = ''; | ||||
457 | 864 | 1088 | my $i; | ||||
458 | 864 | 50 | 33 | 3387 | if (@$body == 2 and ref($body->[0]) eq '') { | ||
459 | # push @{$self->{array}}, $class->_subelement_helper($body->[0], $body->[1], $msg); | ||||||
460 | # return $self; | ||||||
461 | 0 | 0 | $body = [ [$body->[0], $body->[1]] ]; | ||||
462 | } | ||||||
463 | |||||||
464 | 864 | 2599 | BODYPART: for ($i=0; $i < @$body; $i++) { | ||||
465 | 3982 | 6431 | my $bodypart = $body->[$i]; | ||||
466 | # next if ref($bodypart) =~ /)^Business::EDI::/; | ||||||
467 | 3982 | 50 | 9636 | unless (ref($bodypart) eq 'ARRAY') { | |||
468 | 0 | 0 | 0 | warn "Malformed data. Bodypart $i is expected to be pseudohash ARRAYREF, not " | |||
469 | . (ref($bodypart) || "a scalar='$bodypart'") . ". Skipping it..."; | ||||||
470 | 0 | 0 | next; | ||||
471 | } | ||||||
472 | 3982 | 7127 | my $key = $bodypart->[0]; | ||||
473 | 3982 | 50 | 8411 | $debug and print "BODYPART $i: $key\n"; | |||
474 | 3982 | 8962 | while ($j < $codecount) { | ||||
475 | 7559 | 10365 | my $def = $codesref->[$j]; | ||||
476 | 7559 | 0 | 14346 | $debug and printf STDERR "BODYPART $i: $key comparing to def $j: %5s %s\n", $def->{code}, ($key eq $def->{code} ? 'MATCH!' : ''); | |||
50 | |||||||
477 | 7559 | 100 | 19879 | if ($key eq $def->{code}) { | |||
478 | 2496 | 3501 | $last_matched = $key; | ||||
479 | 2496 | 4400 | my $limit = $def->{repeats}; # checking the PREVIOUS def to see if it allows repetition | ||||
480 | 2496 | 50 | 7379 | if (++$repeats <= $limit) { | |||
481 | 2496 | 3117 | push @{$self->{array}}, $class->_subelement_helper($key, $bodypart->[1], $msg); | ||||
2496 | 9560 | ||||||
482 | } else { | ||||||
483 | 0 | 0 | 0 | $strict and die "Code '$key' is limited to $limit occurrences. Dropping data!!"; | |||
484 | 0 | 0 | warn "Code '$key' is limited to $limit occurrences. Dropping data!!"; | ||||
485 | } | ||||||
486 | 2496 | 11955 | next BODYPART; | ||||
487 | } | ||||||
488 | # check if this def was mandatory (satisfied if we already added it) | ||||||
489 | 5063 | 100 | 100 | 16286 | if ($def->{mandatory} and $def->{code} !~ /^UN.$/ and not $repeats) { | ||
100 | |||||||
490 | 186 | 814 | my $msg = "Mandatory code '" . $def->{code} . "' from definition $j missing or out of position (last found '$key' at position $i)"; | ||||
491 | 186 | 50 | 1002 | $strict and return carp_error $msg; | |||
492 | 186 | 50 | 499 | $debug and warn $msg; | |||
493 | } | ||||||
494 | 5063 | 5441 | $repeats = 0; | ||||
495 | 5063 | 11076 | $j++; # move the index to the next rule | ||||
496 | } | ||||||
497 | # now either we matched, or we ran out of tries | ||||||
498 | 1486 | 50 | 3093 | if ($j >= $codecount) { # if we ran out of tries, error | |||
499 | 1486 | 2939 | my $msg = "All $j subelements exhausted. Code '$key' from position $i not matched"; | ||||
500 | 1486 | 50 | 2569 | $strict and return carp_error $msg; | |||
501 | 1486 | 50 | 5031 | $debug and warn $msg; # FIXME: this happens too often | |||
502 | } | ||||||
503 | } | ||||||
504 | 864 | 2625 | return $self; | ||||
505 | # We're out of parts, so time to check for any outstanding mandatory defs (same kind of loop) | ||||||
506 | # This check doesn't work because a subelement can be mandatory in a given optional element. Context matters. | ||||||
507 | 0 | 0 | while (++$j < $codecount) { | ||||
508 | 0 | 0 | 0 | $codesref->[$j]->{mandatory} and return carp_error | |||
509 | "Mandatory code '" . $codesref->[$j]->{code} . "' from definition $j missing (all ". $i+1 . " data traversed)"; | ||||||
510 | } | ||||||
511 | } | ||||||
512 | |||||||
513 | sub _subelement_helper { | ||||||
514 | 2496 | 2496 | 5187 | my ($class, $key, $body, $msg) = @_; | |||
515 | 2496 | 100 | 9276 | if ($key =~ /^[A-Z]{3}$/) { | |||
516 | 1724 | 50 | 4061 | $debug and print STDERR "SEGMENT ($key) detected\n"; | |||
517 | 1724 | 4605 | return $class->segment($key => $body); | ||||
518 | } else { | ||||||
519 | 772 | 3405 | return $class->subelement({$key => $body}, $msg); | ||||
520 | } | ||||||
521 | } | ||||||
522 | |||||||
523 | # Similar to AUTOLOAD, but by an exact argument, does get and set | ||||||
524 | # This code should parallel AUTOLOAD tightly. | ||||||
525 | sub part { | ||||||
526 | 6416 | 6416 | 1 | 163300 | my $self = shift; | ||
527 | 6416 | 50 | 16777 | my $class = ref($self) or croak "part() object method error: $self is not an object"; | |||
528 | 6416 | 50 | 13256 | my $name = shift or return; | |||
529 | |||||||
530 | 6416 | 100 | 18122 | unless (exists $self->{_permitted}->{$name}) { | |||
531 | 3013 | 100 | 6615 | if ($self->{def}) { | |||
532 | 1957 | 100 | 7082 | if ($name =~ s/^all_(.+)$/$1/i) { # strip 'all_' prefix | |||
533 | 1287 | 50 | 2413 | @_ and croak "part() error: all_$name is read_only, rec'd argument(s): " . join(', ', @_); | |||
534 | 1287 | 50 | 2310 | if ($debug) { | |||
535 | 0 | 0 | warn "part() " . $self->{code} . "/all_$name (from " . scalar(@{$self->{array}}) . " arrayed elements): " | ||||
0 | 0 | ||||||
536 | 0 | 0 | . join(' ', map {$_->{code}} @{$self->{array}}); | ||||
0 | 0 | ||||||
537 | 0 | 0 | 0 | $debug > 1 and print STDERR Dumper($self), "\n"; | |||
538 | } | ||||||
539 | 1287 | 100 | 3722 | my $target = $name =~ /^SG\d+$/ ? ($self->{message_code} . "/$name") : $name; | |||
540 | 1287 | 50 | 1282 | return grep {$_->{code} and $_->{code} eq $target} @{$self->{array}}; # return array | |||
12169 | 66059 | ||||||
1287 | 2815 | ||||||
541 | } | ||||||
542 | 670 | 1652 | return __PACKAGE__->_deepload_array($self, $name, @_); # not $self->_deepload_array - avoid recursion | ||||
543 | } | ||||||
544 | 1056 | 2731 | return __PACKAGE__->_deepload($self, $name, @_); # not $self->_deepload - avoid recursion | ||||
545 | } | ||||||
546 | |||||||
547 | 3403 | 50 | 8072 | if (@_) { | |||
548 | 0 | 0 | return $self->{$name} = shift; | ||||
549 | } else { | ||||||
550 | 3403 | 14961 | return $self->{$name}; | ||||
551 | } | ||||||
552 | } | ||||||
553 | |||||||
554 | # part_keys gives you values that are always valid as the argument to the same object's part() method | ||||||
555 | # TODO: mix/match both _permitted and def based? Maybe. | ||||||
556 | |||||||
557 | sub part_keys { | ||||||
558 | 1346 | 1346 | 1 | 40800 | my $self = shift; | ||
559 | 1346 | 50 | 6087 | if ($self->{def}) { | |||
560 | 0 | 0 | 0 | return map { my $key = $_->{code}; $_->{repeats} > 1 ? "all_$key" : $key } @{$self->{def}}; | |||
0 | 0 | ||||||
0 | 0 | ||||||
0 | 0 | ||||||
561 | } | ||||||
562 | 1346 | 1297 | return keys %{$self->{_permitted}}; | ||||
1346 | 6593 | ||||||
563 | # my $spec = $self->spec or croak "You must set a spec version (via constructor or spec method) before EDI can know what parts an $self object might have"; | ||||||
564 | } | ||||||
565 | |||||||
566 | |||||||
567 | # Example data: | ||||||
568 | # 'BGM', { | ||||||
569 | # '1004' => '582822', | ||||||
570 | # '4343' => 'AC', | ||||||
571 | # '1225' => '29', | ||||||
572 | # 'C002' => { | ||||||
573 | # '1001' => '231' | ||||||
574 | # } | ||||||
575 | # } | ||||||
576 | |||||||
577 | our $codelist_map; | ||||||
578 | |||||||
579 | # Tricky recursive constructor! | ||||||
580 | sub subelement { | ||||||
581 | 7883 | 7883 | 0 | 10156 | my $self = shift; | ||
582 | 7883 | 9108 | my $body = shift; | ||||
583 | 7883 | 100 | 66 | 24282 | my $message_code = (@_ and $_[0]) ? shift : ''; | ||
584 | 7883 | 50 | 16039 | if (! $body) { | |||
585 | 0 | 0 | carp "required argument to subelement() empty"; | ||||
586 | 0 | 0 | return; | ||||
587 | } | ||||||
588 | 7883 | 100 | 29169 | unless (ref $body) { | |||
589 | 193 | 50 | 798 | $debug and carp "subelement() got a regular scalar argument. Returning it ('$body') as subelement"; | |||
590 | 193 | 896 | return $body; | ||||
591 | } | ||||||
592 | 7690 | 50 | 17418 | ref($body) =~ /^Business::EDI/ and return $body; # it's already an EDI object, return it | |||
593 | |||||||
594 | 7690 | 50 | 26595 | if (ref($body) eq 'ARRAY') { | |||
50 | |||||||
595 | 0 | 0 | 0 | if (scalar(@$body) != 2) { | |||
596 | 0 | 0 | carp "Array expected to be psuedohash with 2 elements, or wrapper with 1, instead got " . scalar(@$body); | ||||
597 | 0 | 0 | return; # [(map {ref($_) ? $self->subelement($_) : $_} @$body)]; # recursion | ||||
598 | } else { | ||||||
599 | 0 | 0 | $body = {$body->[0] => $body->[1]}; | ||||
600 | } | ||||||
601 | } | ||||||
602 | elsif (ref($body) ne 'HASH') { | ||||||
603 | 0 | 0 | carp "argument to subelement() should be ARRAYref or HASHref or Business::EDI subobject, not type '" . ref($body) . "'"; | ||||
604 | 0 | 0 | return; | ||||
605 | } | ||||||
606 | 7690 | 50 | 0 | 17605 | $debug and print STDERR "good: we now have a body in class " . (ref($self) || $self) . " with " . scalar(keys %$body) . " key(s): ", join(', ', keys %$body), "\n"; | ||
607 | 7690 | 66 | 15077 | $codelist_map ||= Business::EDI::CodeList->codemap; | |||
608 | 7690 | 12737 | my $new = {}; | ||||
609 | 7690 | 24348 | foreach (keys %$body) { | ||||
610 | 7690 | 50 | 16685 | $debug and print STDERR "subelement building from key '$_'\n"; | |||
611 | 7690 | 14565 | my $ref = ref($body->{$_}); | ||||
612 | 7690 | 100 | 100 | 57474 | if ($codelist_map->{$_}) { # If the key is in the codelist map, it's a codelist | ||
100 | |||||||
50 | |||||||
100 | |||||||
100 | |||||||
50 | |||||||
613 | 2944 | 50 | 9575 | $new->{$_} = $self->codelist($_, $body->{$_}) | |||
614 | or carp "Bad ref ($ref) in body for key $_. Codelist subelement not created"; | ||||||
615 | } elsif (/^C\d{3}$/ or /^S\d{3}$/) { | ||||||
616 | 1904 | 50 | 10929 | $new->{$_} = Business::EDI::Composite->new({$_ => $body->{$_}}) # Cxxx and Sxxx codes are for Composite data elements | |||
617 | or carp "Bad ref ($ref) in body for key $_. Composite subelement not created"; | ||||||
618 | } elsif (/^[A-Z]{3}$/) { | ||||||
619 | 0 | 0 | 0 | $new->{$_} = $self->segment($_, $body->{$_}) # ABC codes are for Segments | |||
620 | or carp "Bad ref ($ref) in body for key $_. Segment subelement not created"; | ||||||
621 | } elsif (/^(\S+\/)?(SG\d+)$/) { | ||||||
622 | 772 | 1218 | my $sg_spec = $_; | ||||
623 | 772 | 1797 | my $msg = $1; | ||||
624 | 772 | 1784 | my $sg_tag = $2; | ||||
625 | 772 | 1327 | $sg_spec =~ s/\/\S+\//\//; # delete middle tags: ORDRSP/SG25/SG26 => ORSRSP/SG26 | ||||
626 | 772 | 50 | 5118 | $new->{$sg_spec} = $self->segment_group(($msg ? $sg_spec : "$message_code/$sg_tag"), $body->{$_}, $message_code) # SGx[x] codes are for Segment Groups | |||
50 | |||||||
627 | or carp "Bad ref ($ref) in body for key $_. Segment_group subelement not created"; | ||||||
628 | } elsif ($ref eq 'ARRAY') { | ||||||
629 | 193 | 308 | my $count = scalar(@{$body->{$_}}); | ||||
193 | 544 | ||||||
630 | 193 | 50 | 831 | $count == 1 or carp "Repeated section '$_' appears $count times. Only handling first appearance"; # TODO: fix this | |||
631 | 193 | 727 | $new->{repeats}->{$_} = -1; | ||||
632 | 193 | 50 | 947 | $new->{$_} = $self->subelement($body->{$_}->[0], $message_code) # ELSE, break the ref down (recursively) | |||
633 | or carp "Bad ref ($ref) in body for key $_. Subelement not created"; | ||||||
634 | } elsif ($ref) { | ||||||
635 | 0 | 0 | 0 | $new->{$_} = $self->subelement($body->{$_}, $message_code) # ELSE, break the ref down (recursively) | |||
636 | or carp "Bad ref ($ref) in body for key $_. Subelement not created"; | ||||||
637 | } else { | ||||||
638 | 1877 | 11858 | $new->{$_} = Business::EDI::DataElement->new($_, $body->{$_}); # Otherwise, a terminal (non-ref) data node means it's a DataElement | ||||
639 | # like Business::EDI::DataElement->new('1225', '582830'); | ||||||
640 | } | ||||||
641 | 7690 | 50 | 1831511 | (scalar(keys %$body) == 1) and return $new->{$_}; # important: if that's our only key/pair, return the object itself, no wrapper. | |||
642 | } | ||||||
643 | 0 | 0 | return $new; | ||||
644 | } | ||||||
645 | |||||||
646 | |||||||
647 | # not really xpath, but xpath-lite-like. the idea here is to never crash on a valid path, just return undef. | ||||||
648 | sub xpath { | ||||||
649 | 4137 | 4137 | 1 | 22750 | my $self = shift; | ||
650 | 4137 | 50 | 13855 | my $path = shift or return; | |||
651 | 4137 | 50 | 8008 | my $class = ref($self) or croak "xpath() object method error: $self is not an object"; | |||
652 | 4137 | 50 | 9276 | $path eq '/' and return $self; | |||
653 | 4137 | 50 | 10475 | $path =~ m#([^-A-z_0-9/\.])# and croak "xpath does not handle '$1' in the path, just decending paths like 'SG27/LIN/1229'"; | |||
654 | 4137 | 50 | 7653 | $path =~ m#(//)# and croak "xpath does not handle '$1' in the path, just decending paths like 'SG27/LIN/1229'"; | |||
655 | 4137 | 50 | 6691 | $path =~ m#^/# and croak "xpath does not handle leading slashes in the path, just decending relative paths like 'SG27/LIN/1229'"; | |||
656 | |||||||
657 | 4137 | 9256 | my ($front, $back) = split "/", $path, 2; | ||||
658 | 4137 | 50 | 8819 | defined $front or $front = ''; | |||
659 | 4137 | 100 | 6901 | defined $back or $back = ''; | |||
660 | 4137 | 50 | 7502 | $debug and print STDERR $class . "->xpath($path) ==> ->part($front)->xpath($back);\n"; | |||
661 | |||||||
662 | 4137 | 50 | 7941 | if ($front) { | |||
663 | 4137 | 100 | 10804 | $back or return $self->part($front); # no trailing part means we're done! | |||
664 | 1675 | 1675 | my @ret; | ||||
665 | 1675 | 50 | 3477 | push @ret, $self->part($front) or return; # front might return multiple hits ('all_SG3', for example) | |||
666 | 1675 | 2948 | return grep {defined $_} map {$_->xpath($back)} @ret; | ||||
5384 | 11786 | ||||||
4016 | 8453 | ||||||
667 | } | ||||||
668 | 0 | 0 | croak "xpath does not handle leading slashes in the path, just decending relative paths like 'SG27/LIN/1229'"; | ||||
669 | } | ||||||
670 | |||||||
671 | sub xpath_value { | ||||||
672 | 17 | 17 | 1 | 12932 | my $self = shift; | ||
673 | 17 | 58 | my @hits = $self->xpath(@_); | ||||
674 | 17 | 50 | 68 | @hits or return; | |||
675 | 17 | 100 | 209 | wantarray or return $hits[0]->value; | |||
676 | 2 | 9 | return map {$_->value} @hits; | ||||
72 | 207 | ||||||
677 | } | ||||||
678 | |||||||
679 | our $cgi; | ||||||
680 | # Write your own CSS | ||||||
681 | sub html { | ||||||
682 | 0 | 0 | 0 | 0 | my $self = shift; | ||
683 | 0 | 0 | 0 | my $empties = @_ ? shift : 0; | |||
684 | 0 | 0 | 0 | my $indent = @_ ? shift : 0; | |||
685 | 0 | 0 | 0 | my $obtype = ref $self or return $self; | |||
686 | 0 | 0 | my $x = ' ' x $indent; | ||||
687 | |||||||
688 | 0 | 0 | my $extra = ''; | ||||
689 | 0 | 0 | 0 | $obtype =~ s/^Business::EDI::// or return "$x $obtype object "; |
|||
690 | 0 | 0 | 0 | if ($obtype =~ /::(.*)$/) { | |||
691 | 0 | 0 | $extra = " edi_$1"; | ||||
692 | 0 | 0 | $extra =~ s/::/_/; | ||||
693 | 0 | 0 | $obtype =~ s/::.*$//; | ||||
694 | } | ||||||
695 | |||||||
696 | 0 | 0 | my $html = "$x "; |
||||
697 | 0 | 0 | my %tophash; | ||||
698 | 0 | 0 | foreach (qw/code label desc value/) { # get top values, if existing | ||||
699 | 0 | 0 | 0 | $tophash{$_} = $self->$_ if (eval {$self->$_}); | |||
0 | 0 | ||||||
700 | } | ||||||
701 | 0 | 0 | 0 | $cgi ||= CGI->new(); | |||
702 | 0 | 0 | foreach (qw/code label desc value/) { # same order, w/ some fanciness for label (title attribute based on desc) | ||||
703 | 0 | 0 | 0 | defined $tophash{$_} or next; | |||
704 | 0 | 0 | my $attrs = {class=>"edi_$_"}; | ||||
705 | 0 | 0 | 0 | ($_ eq 'label') and $attrs->{title} = $tophash{desc}; | |||
706 | 0 | 0 | $html .= "\n$x " . $cgi->span($attrs, $self->$_); | ||||
707 | } | ||||||
708 | |||||||
709 | 0 | 0 | 0 | 0 | my @keys = grep {$_ ne 'label' and $_ ne 'value' and $_ ne 'code' and $_ ne 'desc'} $self->part_keys; # disclude stuff we already got | ||
0 | 0 | 0 | |||||
710 | #my @parts = map {$self->part($_)} $self->part_keys; | ||||||
711 | 0 | 0 | 0 | my @parts = $self->{array} ? @{$self->{array}} : map {$self->part($_)} @keys; | |||
0 | 0 | ||||||
0 | 0 | ||||||
712 | 0 | 0 | 0 | $debug and print STDERR $tophash{label}, " has ", scalar(@keys), " in part_keys: ", join(' ', @keys), "\n"; | |||
713 | # $_->{array} and print "$tophash{label} has ", scalar(@{$_->{array}}), " in array: " . join(' ', map {$_->{code}} @{$_->{array}}), "\n"; | ||||||
714 | 0 | 0 | 0 | $debug and print STDERR $tophash{label}, " has ", scalar(@parts), " in 'parts' : ", join(' ', map {ref($_) ? $_->{code} : $_} @parts), "\n"; | |||
0 | 0 | 0 | |||||
715 | 0 | 0 | 0 | if (@parts) { | |||
716 | 0 | 0 | $html .= "\n$x
|
||||
717 | 0 | 0 | foreach (@parts) { | ||||
718 | 0 | 0 | 0 | 0 | (ref $_ and $_->{code}) or next; | ||
719 | 0 | 0 | 0 | $debug and print STDERR "html(): $tophash{label} => " . $_->{code} . " subcall\n"; | |||
720 | 0 | 0 | $html .= "\n$x |
||||
721 | } | ||||||
722 | 0 | 0 | $html .= "\n$x " | ||||
723 | } | ||||||
724 | 0 | 0 | return "$html\n$x"; | ||||
725 | } | ||||||
726 | |||||||
727 | |||||||
728 | 1; | ||||||
729 | |||||||
730 | # END of Business::EDI | ||||||
731 | # ======================================================================================= | ||||||
732 | |||||||
733 | package Business::EDI::Segment_group; | ||||||
734 | 15 | 15 | 163517 | use strict; use warnings; | |||
15 | 15 | 78 | |||||
15 | 826 | ||||||
15 | 98 | ||||||
15 | 38 | ||||||
15 | 2219 | ||||||
735 | 15 | 15 | 465 | use Carp; | |||
15 | 39 | ||||||
15 | 1533 | ||||||
736 | 15 | 15 | 117 | use base qw/Business::EDI/; | |||
15 | 32 | ||||||
15 | 15914 | ||||||
737 | our $VERSION = 0.02; | ||||||
738 | our $debug; | ||||||
739 | |||||||
740 | sub sg_code { | ||||||
741 | 0 | 0 | 0 | 0 | my $self = shift or return; | ||
742 | 0 | 0 | 0 | @_ and croak "sg_code is read only (no args)"; | |||
743 | 0 | 0 | return $self->{sg_code}; | ||||
744 | } | ||||||
745 | |||||||
746 | sub desc { # build a description on the fly | ||||||
747 | 0 | 0 | 0 | 0 | my $self = shift or return; | ||
748 | 0 | 0 | my $sgcode = $self->sg_code; | ||||
749 | 0 | 0 | $sgcode =~ s/^SG//i; | ||||
750 | 0 | 0 | return $self->{message_code} . " Segment Group $sgcode"; | ||||
751 | } | ||||||
752 | |||||||
753 | # Business::EDI::Segment_group gets its own part method to handle meta-mapped SGs INSIDE other SGs, | ||||||
754 | # but it falls back to the main part method after that. | ||||||
755 | |||||||
756 | sub part { | ||||||
757 | 1836 | 1836 | 2033 | my $self = shift; | |||
758 | 1836 | 50 | 3612 | my $class = ref($self) or croak("part object method error: $self is not an object"); | |||
759 | 1836 | 50 | 3202 | my $name = shift or return; | |||
760 | 1836 | 50 | 4552 | my $code = $self->{message_code} or return $self->carp_error("Message type (code) unset. Cannot assess metamapping."); | |||
761 | 1836 | 50 | 4374 | my $spec = $self->{spec} or return $self->carp_error("Message spec (code) unset. Cannot assess metamapping."); | |||
762 | 1836 | 6228 | my $sg = $spec->metamap($code, $name); | ||||
763 | 1836 | 7862 | my $str_spec = "in spec " . $spec->version; | ||||
764 | 1836 | 100 | 3678 | if ($sg) { | |||
765 | 500 | 50 | 838 | $debug and warn "SG Message/field '$code/$name' ==> '$code/all_$sg' via mapping $str_spec"; | |||
766 | 500 | 50 | 1163 | if ($sg =~ /\//) { | |||
767 | 500 | 448 | my $obj; | ||||
768 | 500 | 1383 | my @chunks = split '/', $sg; | ||||
769 | 500 | 725 | my $first = shift @chunks; | ||||
770 | 500 | 811 | my $last = pop @chunks; | ||||
771 | 500 | 50 | 1624 | $first eq $self->{sg_code} or return $self->carp_error("Mapped target $sg descends from $code/$first $str_spec, not " . $self->{sg_code}); | |||
772 | 500 | 908 | foreach (@chunks) { | ||||
773 | 0 | 0 | 0 | $obj = $obj ? $obj->SUPER::part("all_$_") : $self->SUPER::part("all_$_"); | |||
774 | 0 | 0 | 0 | $obj or warn "Mapped SG $sg part 'all_$_' not found $str_spec"; | |||
775 | 0 | 0 | 0 | $obj or return; | |||
776 | } | ||||||
777 | 500 | 50 | 2108 | return $obj ? $obj->SUPER::part("all_$last", @_) : $self->SUPER::part("all_$last", @_); # only the last part gets the remaining args | |||
778 | } else { | ||||||
779 | 0 | 0 | return $self->carp_error("Mapped target $sg is not under " . $self->{code} . " $str_spec"); | ||||
780 | } | ||||||
781 | } else { | ||||||
782 | 1336 | 50 | 2570 | $debug and warn "Message/field '$code/$name' not mapped $str_spec. Skipping metamapping"; | |||
783 | } | ||||||
784 | 1336 | 3025 | return $self->SUPER::part($name, @_); | ||||
785 | } | ||||||
786 | |||||||
787 | |||||||
788 | 1; | ||||||
789 | |||||||
790 | package Business::EDI::Message; | ||||||
791 | 15 | 15 | 123 | use strict; use warnings; | |||
15 | 15 | 36 | |||||
15 | 2269 | ||||||
15 | 264 | ||||||
15 | 34 | ||||||
15 | 546 | ||||||
792 | 15 | 15 | 93 | use Carp; | |||
15 | 34 | ||||||
15 | 1767 | ||||||
793 | 15 | 15 | 95 | use base qw/Business::EDI/; | |||
15 | 39 | ||||||
15 | 16270 | ||||||
794 | our $VERSION = 0.02; | ||||||
795 | our $debug; | ||||||
796 | |||||||
797 | # Business::EDI::Message gets its own part method to handle meta-mapped SGs, | ||||||
798 | # but it falls back to the main part method after that. | ||||||
799 | |||||||
800 | sub part { | ||||||
801 | 121 | 121 | 197 | my $self = shift; | |||
802 | 121 | 50 | 359 | my $class = ref($self) or croak("part object method error: $self is not an object"); | |||
803 | 121 | 50 | 254 | my $name = shift or return; | |||
804 | 121 | 50 | 380 | my $code = $self->{message_code} or return carp_error("Message type (code) unset. Cannot assess metamapping."); | |||
805 | 121 | 50 | 576 | my $spec = $self->{spec} or return carp_error("Message spec (code) unset. Cannot assess metamapping."); | |||
806 | 121 | 473 | my $sg = $spec->metamap($code, $name); | ||||
807 | 121 | 100 | 239 | if ($sg) { | |||
808 | 53 | 105 | $sg =~ s#/#/all_#; # e.g. SG26/SG30 => SG26/all_SG30 | ||||
809 | 53 | 50 | 119 | $debug and warn "Message/field '$code/$name' => '$code/all_$sg' via mapping"; | |||
810 | 53 | 98 | $name = "all_$sg"; # new target from mapping | ||||
811 | } else { | ||||||
812 | 68 | 50 | 163 | $debug and warn "Message/field '$code/$name' not mapped. Skipping metamapping"; | |||
813 | } | ||||||
814 | 121 | 399 | return $self->SUPER::part($name, @_); | ||||
815 | } | ||||||
816 | |||||||
817 | # This is a very high level method. | ||||||
818 | # We look inside a message body BEFORE we know what it is, and what spec it was written to. | ||||||
819 | # Second argument is a flag for "string only", in which case we just return the composed version string (e.g. 'D96A') | ||||||
820 | # otherwise we return a Business::EDI::Message object, or undef on failure. | ||||||
821 | # | ||||||
822 | # my $message = Business:EDI::Message->new($body); | ||||||
823 | # my $version = Business:EDI::Message->new($body, 1); | ||||||
824 | # | ||||||
825 | # Handles ALL valid message types | ||||||
826 | |||||||
827 | sub new { | ||||||
828 | 7 | 7 | 16 | my $class = shift; | |||
829 | 7 | 50 | 31 | my $body = shift or return $class->carp_error("missing required argument to detect_version()"); | |||
830 | 7 | 50 | 35 | ref($body) eq 'ARRAY' or return $class->carp_error("detect_version_string argument must be ARRAYref, not '" . ref($body) . "'"); | |||
831 | 7 | 23 | foreach my $node (@$body) { | ||||
832 | 7 | 24 | my ($tag, $segbody, @xtra) = @$node; | ||||
833 | 7 | 50 | 27 | unless ($tag) { carp "EDI tag received is empty"; next }; | |||
0 | 0 | ||||||
0 | 0 | ||||||
834 | 7 | 50 | 26 | unless ($segbody) { carp "EDI segment '$tag' has no body"; next }; # IIIIIIiiii, ain't got noboooOOoody! | |||
0 | 0 | ||||||
0 | 0 | ||||||
835 | 7 | 50 | 26 | if (scalar @xtra) { carp scalar(@xtra) . " unexpected extra elements encountered in detect_version(). Ignoring!";} | |||
0 | 0 | ||||||
836 | 7 | 50 | 28 | $tag eq 'UNH' or next; | |||
837 | |||||||
838 | 7 | 31 | my $agency = $segbody->{S009}->{'0051'}; # Thankfully these are true in all syntaxes/specs | ||||
839 | 7 | 17 | my $pre = $segbody->{S009}->{'0052'}; | ||||
840 | 7 | 19 | my $release = $segbody->{S009}->{'0054'}; | ||||
841 | 7 | 17 | my $type = $segbody->{S009}->{'0065'}; | ||||
842 | 7 | 50 | 33 | 63 | $agency and $agency eq 'UN' or return $class->carp_error("$tag/S009/0051 does not designate 'UN' as controlling agency"); | ||
843 | 7 | 50 | 33 | 90 | $pre and uc($pre) eq 'D' or return $class->carp_error("$tag/S009/0052 does not designate 'D' as spec (prefix) version"); | ||
844 | 7 | 50 | 31 | $release or return $class->carp_error("$tag/S009/0054 (spec release version) is empty (example value: '96A')"); | |||
845 | |||||||
846 | 7 | 0 | 33 | 28 | @_ and $_[0] and return "$pre$release"; # "string only" | ||
847 | 7 | 50 | 60 | my $edi = Business::EDI->new(version => "$pre$release") or | |||
848 | return $class->carp_error("Spec unrecognized: Failed to create new Business::EDI object with version => '$pre$release'"); | ||||||
849 | 7 | 47 | return $edi->message($type, $body); | ||||
850 | } | ||||||
851 | } | ||||||
852 | |||||||
853 | 1; | ||||||
854 | |||||||
855 | __END__ |