File Coverage

blib/lib/Data/EDI/X12.pm
Criterion Covered Total %
statement 287 303 94.7
branch 108 164 65.8
condition 55 96 57.2
subroutine 13 14 92.8
pod 3 4 75.0
total 466 581 80.2


line stmt bran cond sub pod time code
1             package Data::EDI::X12;
2 3     3   204728 use strict;
  3         27  
  3         104  
3              
4 3     3   1373 use YAML qw(LoadFile Load);
  3         27592  
  3         186  
5 3     3   1481 use IO::File;
  3         25376  
  3         10987  
6              
7             our $VERSION = '0.11';
8              
9             =head1 NAME
10              
11             Data::EDI::X12 - EDI X12 Processing for Perl
12              
13             =cut
14              
15             =head1 SYNOPSIS
16              
17             my $x12 = Data::EDI::X12->new({ spec_file => 'edi.yaml', new_lines => 1, truncate_null => 1, hide_empty_sections => 1 });
18             my $data = $x12->read_record(...);
19             print $x12->write_record($data);
20            
21             =head1 METHODS
22              
23             =cut
24              
25 0     0 0 0 sub debug { shift->{debug} }
26              
27             my $DEFAULT_CONFIG = {
28             ISA => {
29             definition => [
30             {
31             type => 'text',
32             name => 'authorization_information_qualifier',
33             value => '00',
34             bytes => 2,
35             },
36             {
37             type => 'filler',
38             bytes => 10,
39             value => ' ',
40             },
41             {
42             type => 'text',
43             name => 'security_information_qualifier',
44             value => '00',
45             bytes => 2,
46             },
47             {
48             type => 'filler',
49             bytes => 10,
50             value => ' ',
51             },
52             {
53             type => 'text',
54             name => 'interchange_id_qualifier_1',
55             value => '00',
56             bytes => 2,
57             },
58             {
59             type => 'text',
60             name => 'interchange_id_1',
61             value => '00',
62             bytes => 15,
63             },
64             {
65             type => 'text',
66             name => 'interchange_id_qualifier_2',
67             value => '00',
68             bytes => 2,
69             },
70             {
71             type => 'text',
72             name => 'interchange_id_2',
73             value => '00',
74             bytes => 15,
75             },
76             {
77             type => 'text',
78             name => 'date',
79             value => '',
80             bytes => 6,
81             },
82             {
83             type => 'text',
84             name => 'time',
85             value => '',
86             bytes => 4,
87             },
88             {
89             type => 'text',
90             name => 'repetition_separator',
91             value => 'U',
92             bytes => 1,
93             },
94             {
95             type => 'text',
96             name => 'control_version_number',
97             bytes => 5,
98             },
99             {
100             type => 'text',
101             name => 'control_number',
102             bytes => 9,
103             format => '%09i',
104             },
105             {
106             type => 'text',
107             name => 'acknowledgment_requested',
108             bytes => 1,
109             },
110             {
111             type => 'text',
112             name => 'usage_indicator',
113             bytes => 1,
114             value => 'P',
115             },
116             {
117             type => 'text',
118             bytes => 1,
119             value => '>',
120             }
121             ],
122             },
123             IEA => {
124             definition => [
125             {
126             name => 'total',
127             min => 1,
128             max => 10,
129             },
130             {
131             name => 'control_number',
132             min => 4,
133             max => 9,
134             format => '%09i',
135             },
136             ],
137             },
138             GS => {
139             definition => [
140             {
141             type => 'text',
142             name => 'type',
143             value => '00',
144             bytes => 2,
145             },
146             {
147             type => 'text',
148             name => 'sender_code',
149             bytes => 9,
150             },
151             {
152             type => 'text',
153             name => 'receiver_code',
154             bytes => 9,
155             },
156             {
157             type => 'text',
158             name => 'date',
159             value => '',
160             bytes => 8,
161             },
162             {
163             type => 'text',
164             name => 'time',
165             value => '',
166             bytes => 4,
167             },
168             {
169             type => 'text',
170             name => 'control_number',
171             bytes => 9,
172             format => '%09i',
173             },
174             {
175             type => 'text',
176             name => 'agency_code',
177             bytes => 1,
178             value => 'X',
179             },
180             {
181             type => 'text',
182             name => 'version_number',
183             bytes => 6,
184             },
185             ],
186             },
187             ST => {
188             definition => [
189             {
190             name => 'identifier_code',
191             min => 3,
192             max => 3,
193             },
194             {
195             name => 'control_number',
196             min => 4,
197             max => 9,
198             format => '%04i',
199             },
200             ],
201             },
202             SE => {
203             definition => [
204             {
205             name => 'total',
206             min => 1,
207             max => 10,
208             },
209             {
210             name => 'control_number',
211             min => 4,
212             max => 9,
213             format => '%04i',
214             },
215             ],
216             },
217             GE => {
218             definition => [
219             {
220             name => 'total',
221             min => 1,
222             max => 10,
223             },
224             {
225             name => 'control_number',
226             min => 4,
227             max => 9,
228             format => '%09i',
229             },
230             ],
231             },
232             };
233              
234             =head2 new
235              
236             my $x12 = Data::EDI::X12->new({ spec_file => 'edi.yaml', new_lines => 1, truncate_null => 1 });
237              
238             =cut
239              
240             sub new
241             {
242 3     3 1 276 my ($class, $args) = @_;
243              
244 3         7 my $yaml_spec;
245 3 50       17 if ($args->{spec})
    50          
    50          
246             {
247 0         0 $yaml_spec = $args->{spec};
248             }
249             elsif ($args->{yaml_spec})
250             {
251 0         0 $yaml_spec = Load($args->{spec});
252             }
253             elsif ($args->{spec_file})
254             {
255 3         15 $yaml_spec = LoadFile($args->{spec_file});
256             }
257             else
258             {
259 0         0 die sprintf("[%s] args spec or spec_file must be specified", __PACKAGE__);
260             }
261              
262 3         212190 my $spec = {
263             %$DEFAULT_CONFIG,
264             %$yaml_spec,
265             };
266              
267 3         23 my $config_terminator;
268             $config_terminator = $spec->{config}{config_terminator}
269 3 0 33     23 if $spec->{config} and $spec->{config}{config_terminator};
270              
271 3         7 my $config_separator;
272             $config_separator = $spec->{config}{config_separator}
273 3 0 33     13 if $spec->{config} and $spec->{config}{config_separator};
274              
275 3         9 my $config_strict_ascii = $args->{strict_ascii};
276             $config_strict_ascii = $spec->{config}{strict_ascii}
277 3 0 33     20 if $spec->{config} and exists($spec->{config}{strict_ascii});
278              
279 3         9 my $config_truncate_null = $args->{truncate_null};
280             $config_truncate_null = $spec->{config}{truncate_null}
281 3 0 33     11 if $spec->{config} and exists($spec->{config}{truncate_null});
282              
283 3         9 my $config_hide_empty_sections = $args->{hide_empty_sections};
284             $config_hide_empty_sections = $spec->{config}{hide_empty_sections}
285 3 0 33     10 if $spec->{config} and exists($spec->{config}{hide_empty_sections});
286              
287 3         9 my $auto_configure = $args->{auto_configure};
288             $auto_configure = $spec->{config}{auto_configure}
289 3 0 33     13 if $spec->{config} and exists($spec->{config}{auto_configure});
290              
291             my $self = {
292             spec => $spec,
293             debug => $args->{debug},
294             terminator => $config_terminator || $args->{terminator} || '~',
295             separator => $config_separator || $args->{separator} || '*',
296             auto_configure => $auto_configure,
297             error => '',
298             new_lines => $args->{new_lines},
299 3   50     121 truncate_null => $config_truncate_null || 0,
      50        
      50        
      50        
      50        
300             hide_empty_sections => $config_hide_empty_sections || 0,
301             strict_ascii => $config_strict_ascii || 0,
302             };
303 3         11 bless($self);
304              
305 3         24 return $self;
306             }
307              
308             =head2 read_record
309              
310             my $record = $x12->read_record($string);
311              
312             =cut
313              
314             sub read_record
315             {
316 3     3 1 32 my ($self, $string) = @_;
317              
318 3         9 my $record = { };
319              
320 3 100       29 if ($self->{auto_configure})
321             {
322 1         8 my $term_info = $self->_get_autoconfigure_info({ string => $string });
323              
324 1         6 $self->{terminator} = $term_info->{terminator};
325 1         3 $self->{separator} = $term_info->{separator};
326             }
327              
328             # strip newlines if applicable
329             $string =~ s/[\r\n]//g
330 3 50       57 unless $self->{terminator} =~ /[\r\n]/;
331              
332 3     3   75 open(my $fh, "<", \$string);
  3         25  
  3         60  
  3         196  
333              
334             #$self->_parse_transaction_set({
335 3         3388 $self->_parse_edi({
336             fh => $fh,
337             string => $string,
338             record => $record,
339             });
340            
341 3         29 return $record;
342             }
343              
344             =head2 write_record
345              
346             my $string = $x12->write_record($record);
347              
348             =cut
349              
350             sub write_record
351             {
352 3     3 1 22 my ($self, $record) = @_;
353              
354 3         8 my $string = '';
355 3         34 open(my $fh, ">", \$string);
356 3         25 $self->_write_edi({
357             fh => $fh,
358             string => $string,
359             record => $record,
360             });
361            
362 3         23 return $string;
363             }
364              
365             sub _split_string
366             {
367 3     3   9 my ($self, $string) = @_;
368 3         21 my $term_val = quotemeta($self->{terminator});
369 3         9 my $sep_val = quotemeta($self->{separator});
370              
371 3         6 my @records;
372             push @records, [ split(/$sep_val/, $_) ]
373 3         221 for split(/$term_val/, $string);
374              
375 3         19 return @records;
376             }
377              
378             sub _parse_definition
379             {
380 44     44   86 my ($self, $params) = @_;
381              
382 44         71 my $record = { };
383              
384 44         68 my $definition = $params->{definition};
385              
386 44         59 my $segments = $params->{segments};
387 44         73 my $type = $params->{type};
388              
389 44 50       55 for my $def (@{ $definition || [ ] })
  44         112  
390             {
391 227         314 my $segment = shift(@$segments);
392 227         403 $segment =~ s/\s+$//g;
393            
394             $record->{$def->{name}} = $segment
395 227 100       716 if $def->{name};
396             }
397              
398 44         189 return $record;
399             }
400              
401              
402             sub _get_autoconfigure_info
403             {
404 1     1   3 my ($self, $params) = @_;
405              
406 1         5 my $string = "$params->{string}";
407 1         5 $string =~ s/^\s+//g;
408              
409 1         99 my @isa_chars = split(//, $string);
410              
411             return {
412 1         27 separator => $isa_chars[3],
413             terminator => $isa_chars[105],
414             };
415             }
416              
417             sub _parse_edi
418             {
419 3     3   13 my ($self, $params) = @_;
420              
421 3         10 my $fh = $params->{fh};
422 3         6 my $record = $params->{record};
423 3         8 my $definition = $params->{definition};
424 3         6 my $string = $params->{string};
425              
426 3         6 my $IN_ISA = 0;
427 3         7 my $IN_GS = 0;
428 3         5 my $IN_ST = 0;
429 3         5 my $IN_DETAIL = 0;
430 3         6 my $IN_FOOTER = 0;
431              
432 3         12 my $IN_LOOP;
433             my $LOOP_SECTION;
434 3         0 my %LOOP_SEGMENTS;
435              
436 3         0 my ($current_group, $current_set, $current_record);
437              
438             $record->{GROUPS} = [ ]
439 3 50       17 unless exists $record->{GROUPS};
440              
441 3         12 for my $segments ($self->_split_string($string))
442             {
443 53         98 my $type = uc(shift(@$segments));
444              
445 53 100       207 if ($type eq 'ISA')
    100          
    100          
    100          
    100          
    100          
446             {
447             $record->{ISA} = $self->_parse_definition({
448             definition => $self->{spec}->{ISA}->{definition},
449 3         31 segments => $segments,
450             type => $type,
451             });
452              
453 3         9 $IN_ISA = 1;
454              
455 3         8 %LOOP_SEGMENTS = ();
456 3         7 $LOOP_SECTION = undef;
457 3         5 $IN_LOOP = undef;
458              
459 3         7 $IN_DETAIL = 0;
460 3         6 $IN_FOOTER = 0;
461             }
462             elsif ($type eq 'IEA')
463             {
464 3         5 $IN_ISA = 0;
465              
466 3         7 %LOOP_SEGMENTS = ();
467 3         4 $LOOP_SECTION = undef;
468 3         6 $IN_LOOP = undef;
469              
470 3         5 $IN_DETAIL = 0;
471 3         10 $IN_FOOTER = 0;
472             }
473             elsif ($type eq 'GS')
474             {
475             my $new_group = $self->_parse_definition({
476             definition => $self->{spec}->{GS}->{definition},
477 3         26 segments => $segments,
478             type => $type,
479             });
480              
481 3         11 $new_group->{SETS} = [ ];
482              
483 3         7 $IN_GS = 1;
484              
485 3         7 %LOOP_SEGMENTS = ();
486 3         7 $LOOP_SECTION = undef;
487 3         6 $IN_LOOP = undef;
488              
489 3         6 $IN_DETAIL = 0;
490 3         4 $IN_FOOTER = 0;
491              
492 3         7 $current_group = $new_group;
493             }
494             elsif ($type eq 'GE')
495             {
496 3         6 push @{ $record->{GROUPS} }, \%$current_group;
  3         31  
497              
498 3         15 %LOOP_SEGMENTS = ();
499 3         28 $LOOP_SECTION = undef;
500 3         11 $IN_LOOP = undef;
501              
502 3         6 $IN_DETAIL = 0;
503 3         5 $IN_FOOTER = 0;
504              
505 3         8 $IN_GS = 0;
506             }
507             elsif ($type eq 'ST')
508             {
509             my $new_set = $self->_parse_definition({
510             definition => $self->{spec}->{ST}->{definition},
511 3         18 segments => $segments,
512             type => $type,
513             });
514              
515 3         7 $IN_ST = 1;
516              
517 3         7 %LOOP_SEGMENTS = ();
518 3         4 $LOOP_SECTION = undef;
519 3         6 $IN_LOOP = undef;
520              
521 3         4 $IN_DETAIL = 0;
522 3         6 $IN_FOOTER = 0;
523              
524 3         5 $current_set = $new_set;
525 3         7 $current_record = $new_set;
526             }
527             elsif ($type eq 'SE')
528             {
529 3         7 push @{ $current_group->{SETS} }, \%$current_set;
  3         10  
530              
531 3         8 %LOOP_SEGMENTS = ();
532 3         7 $LOOP_SECTION = undef;
533 3         4 $IN_LOOP = undef;
534              
535 3         6 $IN_GS = 0;
536 3         4 $IN_DETAIL = 0;
537 3         6 $IN_FOOTER = 0;
538             }
539             else
540             {
541 35         60 my $doc_id = $current_set->{identifier_code};
542 35         58 my $spec = $self->{spec}->{$doc_id};
543              
544             # parse a record
545 35         46 my %segment_to_section;
546             my %loop_def;
547              
548 35         59 for my $section (qw(footer detail header))
549             {
550 105 50       134 for my $segment (@{ $spec->{structure}{$section} || [ ] })
  105         250  
551             {
552 279 100 66     554 if (ref($segment) and ref($segment) eq 'HASH')
553             {
554 34         66 for my $key (keys(%$segment))
555             {
556 34         63 $loop_def{$key} = $segment->{$key};
557 34         72 $segment_to_section{$key} = uc($section);
558             }
559             }
560             else
561             {
562 245         524 $segment_to_section{$segment} = uc($section);
563             }
564             }
565             }
566              
567             # state machine bingo!
568 35         59 my $section = $segment_to_section{$type};
569              
570 35 100 66     156 if ($section eq 'DETAIL')
    50 33        
    100          
    50          
571             {
572 14         19 $IN_DETAIL = 1;
573             }
574             elsif ($section eq 'HEADER' and $IN_DETAIL)
575             {
576 0         0 $section = 'DETAIL';
577             }
578             elsif ($section eq 'FOOTER')
579             {
580 4         7 $IN_FOOTER = 1;
581             }
582             elsif ($section eq 'DETAIL' and $IN_FOOTER)
583             {
584 0         0 $section = 'FOOTER';
585             }
586              
587 35         45 my $mod_record;
588              
589             # track tree depth
590             # and dump results in loop portion?
591             {
592 35 50       43 if (my $type_def = $spec->{segments}{uc($type)})
  35         87  
593             {
594 35 100 100     107 if ($section eq 'DETAIL' or $LOOP_SECTION eq 'DETAIL')
595             {
596             $current_record->{DETAIL} = [{}]
597 16 100       37 unless exists $current_record->{DETAIL};
598              
599             # START THE LOOPING
600 16 100 100     40 if ($loop_def{$type} and not $IN_LOOP)
601             {
602 1         3 $IN_LOOP = $type;
603 1         2 $LOOP_SECTION = $section;
604 1 50       1 %LOOP_SEGMENTS = map { $_ => 1 } @{ $loop_def{$type} || [] };
  2         7  
  1         4  
605             }
606              
607             # END THE LOOPING
608 16 100 100     49 if ($IN_LOOP and not $LOOP_SEGMENTS{$type})
609             {
610 1         3 %LOOP_SEGMENTS = ();
611 1         2 $LOOP_SECTION = undef;
612 1         2 $IN_LOOP = undef;
613             }
614              
615 16 100       43 if ($IN_LOOP)
616             {
617             $current_record->{DETAIL}->[-1]->{$IN_LOOP} = [{}]
618 4 100       20 unless exists $current_record->{DETAIL}->[-1]->{$IN_LOOP};
619              
620 1         4 push @{ $current_record->{DETAIL}->[-1]->{$IN_LOOP} }, {}
621 4 100       11 if exists($current_record->{DETAIL}->[-1]->{$IN_LOOP}->[-1]->{$type});
622              
623             $current_record->{DETAIL}->[-1]->{$IN_LOOP}->[-1]->{$type} = $self->_parse_definition({
624             definition => $type_def->{definition},
625 4         24 segments => $segments,
626             type => $type,
627             });
628             }
629             else
630             {
631 3         9 push @{ $current_record->{DETAIL} }, {}
632 12 100       29 if exists($current_record->{DETAIL}->[-1]->{$type});
633              
634             $current_record->{DETAIL}->[-1]->{$type} = $self->_parse_definition({
635             definition => $type_def->{definition},
636 12         45 segments => $segments,
637             type => $type,
638             });
639             }
640             }
641             else
642             {
643             $current_record->{$section} = {}
644 19 100       51 unless exists $current_record->{$section};
645              
646 19         39 my $structure = $spec->{structure}{lc($section)};
647              
648             # END THE LOOPING
649 19 100 100     49 if ($IN_LOOP and not $LOOP_SEGMENTS{$type})
650             {
651 1         3 %LOOP_SEGMENTS = ();
652 1         2 $LOOP_SECTION = undef;
653 1         2 $IN_LOOP = undef;
654             }
655              
656             # START THE LOOPING
657 19 100 100     47 if ($loop_def{$type} and not $IN_LOOP)
658             {
659 1         2 $IN_LOOP = $type;
660 1         2 $LOOP_SECTION = $section;
661 1 50       2 %LOOP_SEGMENTS = map { $_ => 1 } @{ $loop_def{$type} || [] };
  2         8  
  1         5  
662              
663             }
664              
665 19 100       46 if ($IN_LOOP)
666             {
667             $current_record->{$LOOP_SECTION}->{$IN_LOOP} = [{}]
668 4 100       10 unless exists $current_record->{$LOOP_SECTION}->{$IN_LOOP};
669              
670 1         3 push @{ $current_record->{$LOOP_SECTION}{$IN_LOOP} }, {}
671 4 100       10 if exists($current_record->{$LOOP_SECTION}->{$IN_LOOP}->[-1]->{$type});
672              
673             $current_record->{$LOOP_SECTION}->{$IN_LOOP}->[-1]->{$type} = $self->_parse_definition({
674             definition => $type_def->{definition},
675 4         32 segments => $segments,
676             type => $type,
677             });
678             }
679             else
680             {
681             $current_record->{$section}->{$type} = $self->_parse_definition({
682             definition => $type_def->{definition},
683 15         73 segments => $segments,
684             type => $type,
685             });
686             }
687             }
688             }
689             }
690             }
691             }
692             }
693              
694             sub _write_spec
695             {
696 53     53   225 my ($self, %params) = @_;
697 53         107 my $type_def = $params{type_def};
698 53         83 my $record = $params{record};
699 53         92 my @line = ($params{type});
700 53         79 my $term_val = $self->{terminator};
701 53         70 my $sep_val = $self->{separator};
702              
703 53 50       66 for my $def (@{ $type_def->{definition} || [ ] })
  53         137  
704             {
705 245 50       473 next unless ref($record) eq 'HASH';
706              
707             my $value = ($def->{name} and exists($record->{$def->{name}})) ?
708 245 100 66     851 $record->{$def->{name}} : $def->{value};
709              
710 245 100       436 $value = '' unless defined $value;
711              
712 245   100     735 $def->{bytes} ||= '';
713              
714             # deal with minimum
715             $def->{bytes} = $def->{min}
716 245 50 100     938 if $value ne '' and not($def->{bytes}) and $def->{min} and length($value) < $def->{min};
      100        
      66        
717              
718             $def->{bytes} = '-' . $def->{bytes}
719 245 100       459 if $def->{bytes};
720              
721 245   66     674 my $format = $def->{format} || "\%$def->{bytes}s";
722            
723             # deal with maximum limits
724             $value = substr($value, 0, $def->{max})
725 245 100       459 if $def->{max};
726              
727             # stop stupidity
728 245 100       615 $value =~ s/\Q$term_val\E//g if $value;
729 245 100       463 $value =~ s/\Q$sep_val\E//g if $value;
730              
731             # strip all non-ascii
732 245 50       417 $value =~ s/[^[:ascii:]]//g if $self->{strict_ascii};
733              
734 245         641 push @line, sprintf($format, $value);
735             }
736              
737            
738 53 50       120 if ($self->{truncate_null})
739             {
740 53         81 for my $val (reverse @line)
741             {
742 56 100       127 last if $val ne '';
743              
744 3         6 pop(@line);
745             }
746             }
747            
748 53         128 my $string = join($sep_val, @line);
749 53         125 $string .= $term_val;
750 53 50       127 $string .= "\n" if $self->{new_lines};
751              
752 53         279 return $string;
753             }
754              
755             sub _write_edi
756             {
757 3     3   8 my ($self, $params) = @_;
758              
759 3         7 my $fh = $params->{fh};
760 3         8 my $record = $params->{record};
761 3         6 my $definition = $params->{definition};
762 3         8 my $string = $params->{string};
763              
764 3         4 my $buffer = '';
765              
766 3         8 my $term_val = $self->{terminator};
767 3         6 my $sep_val = $self->{separator};
768              
769 3         15 my $hide_empty_sections = $self->{hide_empty_sections};
770              
771             $record->{ISA}{control_number} = 1
772 3 50       34 unless exists $record->{ISA}{control_number};
773              
774             # write ISA header
775             print $fh $self->_write_spec(
776             type => 'ISA',
777             type_def => $self->{spec}->{ISA},
778             record => $record->{ISA},
779 3         34 );
780              
781 3         8 my $group_count = 0;
782             # iterate through document structure
783 3 50       8 for my $group (@{ $record->{GROUPS} || [ ] })
  3         13  
784             {
785 3         6 $group_count++;
786              
787 3 50       15 $group->{control_number} = $group_count unless exists $group->{control_number};
788              
789             # process GS line
790             print $fh $self->_write_spec(
791             type => 'GS',
792             type_def => $self->{spec}->{GS},
793 3         15 record => $group,
794             );
795              
796 3         10 my $set_count = 0;
797              
798 3 50       6 for my $set (@{ $group->{SETS} || [ ] })
  3         14  
799             {
800 3         7 my $record_count = 1;
801              
802             # you don't want to know why this exists...
803 3 50       24 if ($self->{spec}->{RECORD_OFFSET_COUNT})
804             {
805 0         0 $record_count = $record_count + $self->{spec}->{RECORD_OFFSET_COUNT};
806             }
807              
808 3         7 $set_count++;
809              
810             $set->{control_number} = $set_count
811 3 50       12 unless exists $set->{control_number};
812              
813             # process ST line
814             print $fh $self->_write_spec(
815             type => 'ST',
816             type_def => $self->{spec}->{ST},
817 3         15 record => $set,
818             );
819              
820             ######
821             # process actual set
822 3         13 my $doc_id = $set->{identifier_code};
823 3         8 my $spec = $self->{spec}->{$doc_id};
824              
825 3 50       18 die "cannot find spec for $doc_id"
826             unless $spec;
827              
828             # process set header
829 3 50       24 for my $section (@{ $spec->{structure}{header} || [ ] })
  3         18  
830             {
831 12 100 66     38 if (ref($section) and ref($section) eq 'HASH')
832             {
833 1         14 my ($loop_name) = keys(%$section);
834 1         4 my $loop_structure = $section->{$loop_name};
835              
836 1 50       2 for my $record (@{ $set->{HEADER}{$loop_name} || [] })
  1         6  
837             {
838 2 50       4 for my $structure (@{ $loop_structure || []})
  2         7  
839             {
840 4 50 33     9 next if $hide_empty_sections and not exists $record->{$structure};
841              
842 4         5 $record_count++;
843              
844             print $fh $self->_write_spec(
845             type => $structure,
846             type_def => $spec->{segments}{$structure},
847 4         16 record => $record->{$structure},
848             );
849             }
850             }
851             }
852             else
853             {
854 11 50 33     26 next if $hide_empty_sections and not exists $set->{HEADER}{$section};
855              
856 11         15 $record_count++;
857             print $fh $self->_write_spec(
858             type => $section,
859             type_def => $spec->{segments}{$section},
860 11         42 record => $set->{HEADER}{$section},
861             );
862             }
863             }
864              
865             # process set details
866 3 50       10 for my $detail (@{ $set->{DETAIL} || [ ] })
  3         16  
867             {
868 6 50       10 for my $section (@{ $spec->{structure}{detail} || [ ] })
  6         35  
869             {
870 14 100 66     64 if (ref($section) and ref($section) eq 'HASH')
871             {
872 2         9 my ($loop_name) = keys(%$section);
873 2         5 my $loop_structure = $section->{$loop_name};
874              
875 2 100       3 for my $sub_record (@{ $detail->{$loop_name} || [] })
  2         11  
876             {
877 2 50       4 for my $structure (@{ $loop_structure || []})
  2         6  
878             {
879 4 50 33     10 next if $hide_empty_sections and not exists $sub_record->{$structure};
880              
881 4         7 $record_count++;
882             print $fh $self->_write_spec(
883             type => $structure,
884             type_def => $spec->{segments}{$structure},
885 4         11 record => $sub_record->{$structure},
886             );
887             }
888             }
889             }
890             else
891             {
892 12 50 33     36 next if $hide_empty_sections and not exists $detail->{$section};
893              
894 12         25 $record_count++;
895             print $fh $self->_write_spec(
896             type => $section,
897             type_def => $spec->{segments}{$section},
898 12         52 record => $detail->{$section},
899             );
900             }
901             }
902             }
903            
904              
905             # process set footer
906 3 50       9 for my $section (@{ $spec->{structure}{footer} || [ ] })
  3         17  
907             {
908 4 50 33     16 if (ref($section) and ref($section) eq 'HASH')
909             {
910 0         0 my ($loop_name) = keys(%$section);
911 0         0 my $loop_structure = $section->{$loop_name};
912              
913 0 0       0 for my $record (@{ $set->{FOOTER}{$loop_name} || [] })
  0         0  
914             {
915 0 0       0 for my $structure (@{ $loop_structure || []})
  0         0  
916             {
917 0 0 0     0 next if $hide_empty_sections and not exists $record->{$structure};
918              
919 0         0 $record_count++;
920             print $fh $self->_write_spec(
921             type => $structure,
922             type_def => $spec->{segments}{$structure},
923 0         0 record => $record->{$structure},
924             );
925             }
926             }
927             }
928             else
929             {
930 4 50 33     24 next if $hide_empty_sections and not exists $set->{FOOTER}{$section};
931              
932 4         7 $record_count++;
933             print $fh $self->_write_spec(
934             type => $section,
935             type_def => $spec->{segments}{$section},
936 4         22 record => $set->{FOOTER}{$section},
937             );
938             }
939             }
940              
941             ######
942              
943             # process SE line
944 3         9 $record_count++;
945             print $fh $self->_write_spec(
946             type => 'SE',
947             type_def => $self->{spec}->{SE},
948             record => {
949             total => $record_count,
950             control_number => $set->{control_number},
951             },
952 3         20 );
953             }
954              
955             # process GE line
956             print $fh $self->_write_spec(
957             type => 'GE',
958             type_def => $self->{spec}->{GE},
959             record => {
960             control_number => $group->{control_number},
961 3         19 total => $set_count,
962             },
963             );
964             }
965              
966             # write IEA header
967             print $fh $self->_write_spec(
968             type => 'IEA',
969             type_def => $self->{spec}->{IEA},
970             record => {
971             control_number => $record->{ISA}{control_number},
972 3         19 total => $group_count,
973             },
974             );
975             }
976              
977             =head1 EXAMPLES
978              
979             =head2 SPEC FILE EXAMPLE
980              
981             850:
982             structure:
983             header:
984             - BEG
985             - DTM
986             - N9
987             - N1
988             detail:
989             - PO1
990             - PID
991             footer:
992             - CTT
993             segments:
994             BEG:
995             definition:
996             - name: purpose_codse
997             min: 2
998             max: 2
999             - name: type_code
1000             min: 2
1001             max: 2
1002             - name: order_number
1003             min: 1
1004             max: 22
1005             - type: filler
1006             - name: date
1007             min: 8
1008             max: 8
1009             DTM:
1010             definition:
1011             - name: qualifier
1012             min: 3
1013             max: 3
1014             - name: date
1015             min: 8
1016             max: 8
1017             N9:
1018             definition:
1019             - name: qualifier
1020             min: 2
1021             max: 3
1022             - name: identification
1023             min: 1
1024             max: 50
1025             N1:
1026             definition:
1027             - name: identifier
1028             min: 2
1029             max: 3
1030             - name: name
1031             min: 1
1032             max: 60
1033             - name: identification_code_qualifier
1034             min: 1
1035             max: 2
1036             - name: identification_code
1037             min: 2
1038             max: 80
1039             PO1:
1040             definition:
1041             - type: filler
1042             - name: quantity
1043             min: 1
1044             max: 15
1045             - name: unit_of_measure
1046             min: 2
1047             max: 2
1048             - name: unit_price
1049             min: 1
1050             max: 17
1051             - type: filler
1052             - name: id_qualifier
1053             min: 2
1054             max: 2
1055             - name: product_id
1056             min: 1
1057             max: 48
1058             - name: id_qualifier_2
1059             min: 2
1060             max: 2
1061             - name: product_id_2
1062             min: 1
1063             max: 48
1064             - name: id_qualifier_3
1065             min: 2
1066             max: 2
1067             - name: product_id_3
1068             min: 1
1069             max: 48
1070             PID:
1071             definition:
1072             - name: type
1073             - type: filler
1074             - type: filler
1075             - type: filler
1076             - name: description
1077             min: 1
1078             max: 80
1079             CTT:
1080             definition:
1081             - name: total_items
1082             min: 1
1083             max: 6
1084             - name: hash_total
1085             min: 1
1086             max: 10
1087              
1088             =head2 PERL EXAMPLE
1089              
1090             use Data::EDI::X12;
1091            
1092             my $string = q[ISA*00* *00* *01*012345675 *01*987654321 *140220*1100*^*00501*000000001*0*P*>~
1093             GS*PO*012345675*987654321*20140220*1100*000000001*X*005010~
1094             ST*850*0001~
1095             BEG*00*KN*1136064**20140220~
1096             DTM*002*20140220~
1097             N9*ZA*0000010555~
1098             N1*ST*U997*92*U997~
1099             PO1**1*EA*1.11**UC*000000000007*PI*000000000000000004*VN*113~
1100             PID*F****Test Product 1~
1101             PO1**1*EA*2.22**UC*000000000008*PI*000000000000000005*VN*114~
1102             PID*F****Test Product 2~
1103             CTT*4*4~
1104             SE*12*0001~
1105             GE*1*000000001~
1106             IEA*1*000000001~
1107             ];
1108            
1109             my $x12 = Data::EDI::X12->new({ spec_file => 't/spec.yaml', new_lines => 1, truncate_null => 1 });
1110            
1111             my $record = $x12->read_record($string);
1112             my $out = $x12->write_record($record);
1113              
1114             =head2 LOOPS
1115              
1116             Both implicit and explicit loop segments are also supported by this module. Please review the loops test for an example.
1117              
1118             =head1 HISTORY
1119              
1120             This module was authored for L.
1121              
1122             =head1 AUTHOR
1123              
1124             Bizowie
1125              
1126             =head1 COPYRIGHT AND LICENSE
1127              
1128             Copyright (C) 2014, 2015, 2016 Bizowie
1129              
1130             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.14.2 or, at your option, any later version of Perl 5 you may have available.
1131              
1132             =cut
1133              
1134             1;