File Coverage

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
  • \n" . $_->html($empties, $indent + 8) . "\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__