File Coverage

blib/lib/XML/Edifact.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             #
2             # Copyright (c) 1998 Michael Koehne
3             #
4             # XML::Edifact is free software. You can redistribute and/or
5             # modify this copy under terms of GNU General Public License.
6              
7             package XML::Edifact;
8              
9 1     1   801 use 5.006;
  1         4  
  1         44  
10 1     1   6 no warnings 'utf8';
  1         1  
  1         42  
11 1     1   1149 use bytes;
  1         16  
  1         5  
12              
13 1     1   31 use strict;
  1         2  
  1         34  
14 1     1   571 use XML::Edifact::Config;
  1         2  
  1         28  
15 1     1   1066 use SDBM_File;
  1         3002  
  1         53  
16 1     1   10 use Fcntl;
  1         3  
  1         349  
17 1     1   1687 use XML::Parser;
  0            
  0            
18             use Carp;
19              
20             use vars qw($VERSION $debug);
21              
22             $VERSION='0.47';
23             $debug=1; # debug=1 is fine
24              
25             # ------------------------------------------------------------------------------
26             # edit the HERE documents for those variables for your systems preferences.
27              
28             use vars qw(
29             $MESSAGE_NAMESPACE
30             $MESSAGE_HEADER
31             );
32              
33             sub eval_xml_edifact_headers {
34             $MESSAGE_NAMESPACE = "edifact" unless $MESSAGE_NAMESPACE;
35              
36             $MESSAGE_HEADER=<
37            
38            
39             <$MESSAGE_NAMESPACE:message
40             xmlns:$MESSAGE_NAMESPACE='$XML::Edifact::Config::URL/LIB/xml-edifact-03/$MESSAGE_NAMESPACE.rdf'
41             xmlns:trsd='$XML::Edifact::Config::URL/LIB/xml-edifact-03/trsd.rdf'
42             xmlns:trcd='$XML::Edifact::Config::URL/LIB/xml-edifact-03/trcd.rdf'
43             xmlns:tred='$XML::Edifact::Config::URL/LIB/xml-edifact-03/tred.rdf'
44             xmlns:uncl='$XML::Edifact::Config::URL/LIB/xml-edifact-03/uncl.rdf'
45             xmlns:anxs='$XML::Edifact::Config::URL/LIB/xml-edifact-03/anxs.rdf'
46             xmlns:anxc='$XML::Edifact::Config::URL/LIB/xml-edifact-03/anxc.rdf'
47             xmlns:anxe='$XML::Edifact::Config::URL/LIB/xml-edifact-03/anxe.rdf'
48             xmlns:unsl='$XML::Edifact::Config::URL/LIB/xml-edifact-03/unsl.rdf'
49             xmlns:unknown='$XML::Edifact::Config::URL/LIB/xml-edifact-03/unknown.rdf' >
50             HERE_MESSAGE_HEADER
51             $MESSAGE_HEADER =~ s/^\t\t//;
52             $MESSAGE_HEADER =~ s/\n\t\t/\n/g;
53             }
54              
55             # end of sub eval_xml_edifact_headers
56             # ------------------------------------------------------------------------------
57              
58             use vars qw(%SEGMT %COMPT %CODET %ELEMT);
59             use vars qw(%SEGMR %EXTEND);
60              
61             use vars qw($edi_message $xml_message @xml_msg);
62             use vars qw($advice $advice_component_seperator);
63             use vars qw($advice_element_seperator $advice_decimal_notation);
64             use vars qw($advice_release_indicator $advice_segment_terminator);
65             use vars qw($indent_join $indent_tab);
66             use vars qw($patch_segment $patch_composite $last_segment $last_composite);
67             use vars qw($coded_entry);
68              
69             # ------------------------------------------------------------------------------
70              
71             sub open_dbm {
72             my ($directory,$fcntl) = @_;
73              
74             $directory = $XML::Edifact::Config::DAT unless $directory;
75             $fcntl = O_RDONLY unless $fcntl;
76              
77             tie(%SEGMT, 'SDBM_File', $directory.'/segment.dat', $fcntl, 0644) || die "can not tie segment.dat:".$!;
78             tie(%SEGMR, 'SDBM_File', $directory.'/segment.rev', $fcntl, 0644) || die "can not tie segment.dat:".$!;
79             tie(%COMPT, 'SDBM_File', $directory.'/composite.dat', $fcntl, 0644) || die "can not tie composite.dat:".$!;
80             tie(%ELEMT, 'SDBM_File', $directory.'/element.dat', $fcntl, 0644) || die "can not tie element.dat:".$!;
81             tie(%CODET, 'SDBM_File', $directory.'/codes.dat', $fcntl, 0644) || die "can not tie codes.dat:".$!;
82              
83             $indent_join='';
84             $indent_tab='';
85             eval_xml_edifact_headers();
86             }
87              
88             sub close_dbm {
89             untie(%SEGMT);
90             untie(%SEGMR);
91             untie(%COMPT);
92             untie(%ELEMT);
93             untie(%CODET);
94             }
95              
96             # -----------------------------------------------------------------------------
97              
98             sub recode_mark {
99             my($mark) = @_;
100             my($M,$s);
101              
102             $M = lc($mark);
103             $s = '[^a-z][^a-z]*', $M =~ s/$s/_/g;
104             # $s = "_coded\$", $M =~ s/$s//;
105             $s = "__*\$", $M =~ s/$s//;
106             $s = "^__*", $M =~ s/$s//;
107             $s = '__*', $M =~ s/$s/./g;
108             return($M);
109             }
110              
111             # -----------------------------------------------------------------------------
112              
113             sub read_edi_message {
114             my($filename) = @_;
115             my($size);
116              
117             $size=(stat($filename))[7] || die "cant stat ".$filename;
118             die $filename." is to short ".$size." for EDI" if ($size <= 9);
119             open(F,$filename) || die "cant open ".$filename;
120             read(F,$edi_message,$size,0) || die "cant read message from ".$filename;
121             close(F);
122              
123             $advice=substr($edi_message,0,9);
124             die $filename." is not an EDI message" if ($advice !~ "^UN[A-Z]");
125              
126             $advice = "UNA:+.? '" unless ($advice =~ "^UNA");
127              
128             $advice_component_seperator =substr($advice,3,1);
129             $advice_element_seperator =substr($advice,4,1);
130             $advice_decimal_notation =substr($advice,5,1);
131             $advice_release_indicator =substr($advice,6,1);
132             $advice_segment_terminator =substr($advice,8,1);
133             }
134              
135             use vars qw($cooked_element_substitute $cooked_release_substitute);
136             use vars qw($cooked_message_substitute $cooked_segment_substitute);
137             use vars qw($component_split $element_split);
138              
139             sub make_xml_message {
140             my ($iofile) = @_;
141              
142             @xml_msg = ();
143             push @xml_msg, $MESSAGE_HEADER;
144             $xml_msg[0] =~ s!\n!!g unless $indent_join;
145             $xml_msg[0] =~ s!\t! !g unless $indent_tab;
146              
147             my($cooked_message,@Segments,$segment,$s,$stating);
148              
149             $cooked_release_substitute = "\\".$advice_release_indicator."\\".$advice_release_indicator;
150             $cooked_message_substitute = "\\".$advice_release_indicator."\\".$advice_segment_terminator;
151             $cooked_segment_substitute = "\\".$advice_release_indicator."\\".$advice_element_seperator;
152             $element_split = "\\".$advice_element_seperator;
153             $cooked_element_substitute = "\\".$advice_release_indicator."\\".$advice_component_seperator;
154             $component_split = "\\".$advice_component_seperator;
155              
156             $cooked_message = $edi_message;
157             $cooked_message =~ s/$cooked_release_substitute/\002/g;
158             $cooked_message =~ s/$cooked_message_substitute/\001/g;
159              
160             @Segments = split /$advice_segment_terminator/, $cooked_message;
161             shift @Segments if ($Segments[0] =~ "^UNA");
162              
163             if ($Segments[0] =~ "^UNB.UNO") {
164             $stating=substr $Segments[0], 7, 1;
165             die "only UNOA to UNOC stating yet implemented"
166             unless "ABC" =~ /$stating/;
167             } else {
168             $stating="C";
169             }
170              
171             if ($iofile) {
172             $iofile->print( (join $indent_join,@xml_msg).$indent_join );
173             @xml_msg = ();
174             }
175              
176             foreach $segment (@Segments) {
177             $segment =~
178             s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg
179             if $stating eq "C";
180             $segment =~ s/\001/$advice_segment_terminator/g;
181             resolve_segment($segment);
182             if ($iofile) {
183             resolve_tabs() if $indent_tab;
184             $iofile->print( (join $indent_join,@xml_msg).$indent_join );
185             @xml_msg = ();
186             }
187             }
188             push @xml_msg , "";
189              
190             if ($iofile) {
191             $iofile->print( (join $indent_join,@xml_msg).$indent_join );
192             @xml_msg = ();
193             } else {
194             resolve_tabs() if $indent_tab;
195             $xml_message = (join $indent_join,@xml_msg).$indent_join;
196             }
197             }
198              
199             # -----------------------------------------------------------------------------
200              
201             sub resolve_segment {
202             my($raw_segment) = @_;
203             my($cooked_segment,@Elements,@Codes,$element,$s,$i);
204             my($sg,@sgv);
205             my($comment);
206              
207             $s="^[ \\n\\r\\t]\$";
208             if ($raw_segment !~ /$s/ ) {
209              
210             if ($debug) {
211             $comment = $raw_segment;
212             $comment =~ s/--/__/g;
213             push @xml_msg, '';
214             }
215              
216             $cooked_segment = $raw_segment;
217             $cooked_segment =~ s/$cooked_segment_substitute/\001/g;
218              
219             @Elements = split /$element_split/, $cooked_segment;
220             @sgv = split "\t", $SEGMT{$Elements[0]}, 4;
221             @Codes = split / /, " ".$sgv[0];
222              
223             push @xml_msg, '' if ($debug>2);
224             if (($sgv[2] ne '') && ($#Codes>=$#Elements)) {
225             push @xml_msg, '<'.$sgv[2].'>';
226             $last_segment = $#xml_msg;
227             undef $last_composite;
228              
229             foreach $i (1 .. $#Elements) {
230             $element = $Elements[$i];
231             $element =~ s/\001/$advice_element_seperator/g;
232              
233             if ($Elements[$i] ne '') {
234             # resolve_element
235             push @xml_msg, '' if ($debug>1);
236             resolve_element($Codes[$i], $Elements[$i]);
237             }
238             }
239             if ($patch_segment) {
240             push @xml_msg, '';
241             undef $patch_segment;
242             } else {
243             push @xml_msg, '';
244             }
245             } else {
246             push @xml_msg, '';
247             }
248             }
249             }
250              
251             # -----------------------------------------------------------------------------
252              
253             sub resolve_element {
254             my($code, $raw_element) = @_;
255             my($cooked_element,@Components,@Codes,$component,$s,$i);
256             my($cm,@cmv);
257             my($ok);
258              
259             $ok=0;
260              
261             push @xml_msg, '' if ($debug>1);
262              
263             $cooked_element = $raw_element;
264             $cooked_element =~ s/$cooked_element_substitute/\001/g;
265              
266             if (($code =~ /^[CS]/) && ($cm = $COMPT{$code})) {
267              
268             @cmv = split("\t", $cm, 4);
269              
270             if ($cmv[2]) {
271             push @xml_msg, '<'.$cmv[2].'>';
272             $last_composite = $#xml_msg;
273             }
274              
275             @Components = split /$component_split/, $cooked_element;
276             @Codes = split / /, $cmv[0];
277              
278             foreach $i (0 .. $#Components) {
279             $component = $Components[$i];
280             if ($component ne '') {
281             $component =~ s/\001/$advice_component_seperator/g;
282             resolve_code($Codes[$i], $component);
283             }
284             }
285              
286             if ($cmv[2]) {
287             if ($patch_composite) {
288             push @xml_msg, '';
289             undef $patch_composite;
290             } else {
291             push @xml_msg, '';
292             }
293             }
294             $ok=1;
295             }
296             if (($code =~ "^[0-9]") && ($cm = $ELEMT{$code})) {
297             $cooked_element =~ s/\001/$advice_component_seperator/g;
298             resolve_code($code, $cooked_element);
299             $ok=1;
300             }
301             push @xml_msg, '' unless $ok;
302             }
303              
304             # -----------------------------------------------------------------------------
305              
306             sub encode_xml {
307             my ($val) = @_;
308              
309             $val =~ s/\002/$advice_release_indicator/g;
310             $val =~ s/&/\&/g;
311             $val =~ s/
312              
313             return $val;
314             }
315              
316             sub resolve_code {
317             my ($code, $val) = @_;
318             my ($cd,@cdv,$enc);
319              
320             my ($mark,$coded) = split / /, $ELEMT{$code};
321              
322             push @xml_msg, '' if ($debug>1);
323              
324             if ($coded) {
325             $cd = $CODET{$code."\t".$val};
326             if ($cd = $CODET{$code."\t".$val}) {
327             @cdv=split /\t/, $cd;
328             push @xml_msg, '<'.$mark.' '.$cdv[0].':code="'.$code.':'.encode_xml($val).'">'.encode_xml($cdv[1]).'';
329             } elsif ($cd = $EXTEND{"code:".$code.":".$val}) {
330             @cdv=split /\t/, $cd;
331             $mark =~ s/^[^:]+:/$MESSAGE_NAMESPACE:/;
332             push @xml_msg, '<'.$mark.' '.$cdv[0].':code="'.$code.':'.encode_xml($val).'">'.encode_xml($cdv[1]).'';
333             $xml_msg[$last_segment] =~ s/^<[^:]+:/<$MESSAGE_NAMESPACE:/;
334             $patch_segment = $xml_msg[$last_segment];
335             $patch_segment =~ s/^
336             $patch_segment =~ s/[ >].*$//;
337             if ($last_composite) {
338             $xml_msg[$last_composite] =~ s/^<[^:]+:/<$MESSAGE_NAMESPACE:/;
339             $patch_composite = $xml_msg[$last_composite];
340             $patch_composite =~ s/^
341             $patch_composite =~ s/[ >].*$//;
342             }
343             } else {
344             $enc = encode_xml($val);
345             push @xml_msg, '<'.$mark. ' unknown:code="'.$code.':'.$enc.'">'.$enc. '';
346             }
347             }
348             else {
349             push @xml_msg, '<'.$mark.'>'.encode_xml($val).'';
350             }
351             }
352              
353             # -----------------------------------------------------------------------------
354              
355             sub resolve_tabs {
356             my ($i,$v,$d);
357              
358             $d = 0;
359             foreach $i (0 .. $#xml_msg) {
360             $v=$xml_msg[$i];
361             $v =~ s/^[ \t]+//;
362             $d-- if (($v =~ m!^0));
363             $xml_msg[$i] = $indent_tab x $d . $v;
364             $d++ if (($v =~ m!^<[a-z]!) && ($v !~ m!!));
365             }
366             }
367              
368             # -----------------------------------------------------------------------------
369              
370             use vars qw(@edi_segment @edi_group $edi_valid $edi_level $edi_si $edi_gi);
371             use vars qw($xml_file $edi_file);
372              
373             sub read_xml_message {
374             my($filename) = @_;
375             my($size);
376              
377             $xml_file = $filename;
378             $xml_message = undef;
379              
380             $size=(stat($filename))[7] || die "cant stat ".$filename;
381             if ($size < 65*1024) {
382             die $filename." is to short ".$size." for EDI" if ($size <= 9);
383             open(F,$filename) || die "cant open ".$filename;
384             read(F,$xml_message,$size,0) || die "cant read message from ".$filename;
385             close(F);
386             }
387              
388             $advice_component_seperator = ":" unless $advice_component_seperator;
389             $advice_element_seperator = "+" unless $advice_element_seperator;
390             $advice_decimal_notation = "." unless $advice_decimal_notation;
391             $advice_release_indicator = "?" unless $advice_release_indicator;
392             $advice_segment_terminator = "'" unless $advice_segment_terminator;
393             }
394              
395             sub make_edi_message {
396             my ($iofile) = @_;
397              
398             $edi_file = $iofile;
399              
400             my $xml_parser;
401              
402             $edi_message = "UNA";
403             $edi_message .= $advice_component_seperator;
404             $edi_message .= $advice_element_seperator;
405             $edi_message .= $advice_decimal_notation;
406             $edi_message .= $advice_release_indicator;
407             $edi_message .= " ";
408             $edi_message .= $advice_segment_terminator;
409             $edi_file->print($edi_message) if $edi_file;
410              
411             $edi_level = 0;
412              
413             $xml_parser = new XML::Parser(Handlers => { Start => \&handle_start,
414             End => \&handle_end,
415             Char => \&handle_char});
416             if ($xml_message) {
417             $xml_parser -> parse($xml_message);
418             } elsif ($xml_file) {
419             $xml_parser -> parsefile($xml_file);
420             }
421              
422             $edi_message = "" if $edi_file;
423             $edi_file = undef;
424              
425             return $edi_message;
426             }
427              
428             sub handle_start {
429             my $expat = shift @_;
430             my $element = shift @_;
431             my %options = @_;
432             my ($opt,$val,$i,$j);
433             my (@sgv,@cmv,@sgc,@cmc,$junk,$trans,$coded);
434              
435             if ($debug>1) {
436             printf STDERR "(%s\n", $element;
437             foreach $opt (keys (%options)) {
438             printf STDERR "A%s=%s\n", $opt, $options{$opt};
439             }
440             }
441              
442             $coded_entry = 0;
443              
444             if ($edi_level == 0) {
445             die "this is not XML::Edifact" if ($element !~ /^[^:]*:message/);
446             $edi_valid = 1;
447             }
448             if ($edi_level == 1) {
449             if ($element =~ /^[^:]*:raw_segment/) {
450             foreach $opt (keys (%options)) {
451             if ($opt eq "data") {
452             $edi_message = "" if $edi_file;
453             $edi_message .= $options{$opt};
454             $edi_message .= $advice_segment_terminator;
455             $edi_file->print($edi_message) if $edi_file;
456             }
457             }
458             @edi_segment = ();
459             $edi_si = 0;
460             @edi_group = ();
461             $edi_gi = 0;
462             $edi_valid = 1;
463             } else {
464             $edi_valid = $SEGMR{$element};
465             $edi_valid = $EXTEND{"sgmt:".$element} unless ($edi_valid || ($XML::Edifact::MESSAGE_NAMESPACE eq "edifact"));
466             if ($edi_valid) {
467             @edi_segment = ($edi_valid);
468             $edi_valid = 1;
469             }
470             $edi_si = 0;
471             @edi_group = ();
472             $edi_gi = 0;
473             }
474             }
475             # to constrain edi_valid from edi_level 2 up adds robustness
476             if ($edi_valid) {
477             if ($edi_level == 2) {
478             $edi_si++;
479             @edi_group = ();
480             $edi_gi = 0;
481              
482             @sgv = split("\t", $SEGMT{$edi_segment[0]}, 4);
483             @sgc = split / /, " ".$sgv[0];
484              
485             SKIP_SEGMT: for ($i = $edi_si; $i <= $#sgc; $i++) {
486             if ($sgc[$i] =~ /^[A-Z]/) {
487             ($junk, $junk, $trans, $junk) = split /\t/, $COMPT{$sgc[$i]};
488             } else {
489             ($trans,$coded) =split / /, $ELEMT{$sgc[$i]}, 2;
490             }
491             last SKIP_SEGMT if ($trans eq $element);
492             }
493              
494             if ($i > $#sgc) {
495             if ($XML::Edifact::MESSAGE_NAMESPACE ne "edifact") {
496             $edi_valid = $EXTEND{"elmt:".$edi_segment[0].":".$element};
497             $edi_valid = $EXTEND{"comp:".$edi_segment[0].":".$element} unless $edi_valid;
498             if ($edi_valid) {
499             ($i,$j) = split / /, $edi_valid;
500             $edi_valid = 1;
501             }
502             } else {
503             $edi_valid = 0;
504             }
505             }
506              
507             if ($i <= $#sgc) {
508             $edi_si = $i;
509             foreach $opt (keys (%options)) {
510             if ($opt =~ "^[^:]*:code") {
511             $val = $options{$opt};
512             $val =~ s/^[^:]*://;
513             $edi_group[$edi_gi] = $val;
514             $coded_entry = 1;
515             }
516             }
517             }
518             }
519             if ($edi_level == 3) {
520             @sgv = split("\t", $SEGMT{$edi_segment[0]}, 4);
521             @sgc = split / /, " ".$sgv[0];
522             @cmv = split("\t", $COMPT{$sgc[$edi_si]}, 4);
523             @cmc = split / /, $cmv[0];
524              
525             SKIP_COMPT: for ($i = $edi_gi; $i <= $#cmc; $i++) {
526             ($trans,$coded) =split / /, $ELEMT{$cmc[$i]}, 2;
527             last SKIP_COMPT if ($trans eq $element);
528             }
529              
530             if ($i <= $#cmc) {
531             } else {
532             $edi_valid = 0;
533             }
534             if ($i > $#cmc) {
535             if ($XML::Edifact::MESSAGE_NAMESPACE ne "edifact") {
536             $edi_valid = $EXTEND{"elmt:".$edi_segment[0].":".$element};
537             if ($edi_valid) {
538             ($j,$i) = split / /, $edi_valid;
539             $edi_valid = 1;
540             }
541             } else {
542             $edi_valid = 0;
543             }
544             }
545              
546             if ($i <= $#cmc) {
547             $edi_gi = $i;
548              
549             foreach $opt (keys (%options)) {
550             if ($opt =~ "^[^:]*:code") {
551             $val = $options{$opt};
552             $val =~ s/^[^:]*://;
553             $edi_group[$edi_gi] = $val;
554             $coded_entry = 1;
555             }
556             }
557             }
558             }
559             }
560              
561             carp "invalid xml-edifact at $edi_level level $element" unless $edi_valid;
562              
563             $edi_level++;
564             }
565              
566             sub handle_end {
567             my ($expat, $element) = @_;
568             my ($i,$cooked,$si,$s1,$s2);
569              
570             if ($debug>1) {
571             printf STDERR ")%s\n", $element;
572             }
573              
574             $edi_level--;
575              
576             if ($edi_valid) {
577             if ($edi_level == 1) {
578             if ($#edi_segment>0) {
579             $edi_message = "" if $edi_file;
580             $edi_message .= join $advice_element_seperator, @edi_segment;
581             $edi_message .= $advice_segment_terminator;
582             $edi_file->print($edi_message) if $edi_file;
583             }
584             }
585             if ($edi_level == 2) {
586             for ($i = 0; $i<= $#edi_group; $i++) {
587             $cooked = $edi_group[$i];
588             $cooked =~ s/^[\n\r\t ]*//;
589             $cooked =~ s/[\n\r\t ]*$//;
590              
591             foreach $si ($advice_release_indicator, $advice_component_seperator, $advice_element_seperator, $advice_segment_terminator) {
592             $s1 = "\\".$si;
593             $s2 = $advice_release_indicator.$si;
594             $cooked =~ s/$s1/$s2/g;
595             }
596              
597             $edi_group[$i] = $cooked;
598             }
599             $edi_segment[$edi_si] .= join $advice_component_seperator, @edi_group;
600             }
601             if ($edi_level == 3) {
602             $edi_gi++;
603             }
604             }
605             }
606              
607             sub handle_char {
608             my ($expat, $element) = @_;
609              
610             if ($element !~ /^[\n\r\t ]*$/) {
611             if ($debug>1) {
612             printf STDERR "-%s\n", $element;
613             }
614              
615             $element =~ s/([\xC0-\xDF])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg;
616              
617             $edi_group[$edi_gi] .= $element unless ($coded_entry);
618             }
619             }
620              
621             # ------------------------------------------------------------------------------
622             1; # modules must return true
623             # ------------------------------------------------------------------------------
624             __END__