File Coverage

blib/lib/Data/EDI/X12.pm
Criterion Covered Total %
statement 293 302 97.0
branch 108 150 72.0
condition 36 58 62.0
subroutine 15 16 93.7
pod 3 4 75.0
total 455 530 85.8


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