File Coverage

blib/lib/X12/Parser.pm
Criterion Covered Total %
statement 124 152 81.5
branch 29 38 76.3
condition n/a
subroutine 16 20 80.0
pod 12 12 100.0
total 181 222 81.5


line stmt bran cond sub pod time code
1             # Copyright 2009 by Prasad Balan
2             # All rights reserved.
3             #
4             # This library is free software; you can redistribute it and/or modify
5             # it under the same terms as Perl itself.
6             package X12::Parser;
7 2     2   16774 use strict;
  2         6  
  2         234  
8             require Exporter;
9             our @ISA = qw(Exporter);
10              
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14             # This allows declaration use X12 ':all';
15             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
16             # will save memory.
17             our %EXPORT_TAGS = (
18             'all' => [
19             qw(
20             )
21             ]
22             );
23             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24             our @EXPORT = qw(
25             );
26             our $VERSION = '0.80';
27              
28             # Preloaded methods go here.
29 2     2   1043 use X12::Parser::Tree;
  2         4  
  2         93  
30 2     2   961 use X12::Parser::Cf;
  2         5  
  2         3236  
31              
32             #constructor.
33             sub new {
34 2     2 1 56 my $self = {
35             file => undef,
36             conf => undef,
37             _TREE_ROOT => undef,
38             _TREE_POS => undef,
39             _FILE_HANDLE => undef,
40             _FILE_CLOSED => undef,
41             _SEGMENT_SEPARATOR => undef,
42             _ELEMENT_SEPARATOR => undef,
43             _SUBELEMENT_SEPARATOR => undef,
44             _NEXT_LOOP => undef,
45             _NEXT_SEGMENT => undef,
46             };
47 2         8 return bless $self;
48             }
49              
50             #public method, takes the X12 handle and Cf file name as input.
51             #loads the config file and sets the separators.
52             sub parse {
53 5     5 1 276 my $self = shift;
54 5         16 my %params = @_;
55 5         13 $self->{handle} = $params{handle};
56 5         9 $self->{conf} = $params{conf};
57 5         9 $self->{_FILE_HANDLE} = $params{handle};
58 5         9 $self->{_FILE_CLOSED} = undef;
59              
60             #read the config file to create the TREE object
61 5         32 my $cf = X12::Parser::Cf->new();
62 5 50       38 $self->{_TREE_ROOT} = $cf->load( file => "$self->{conf}" )
63             if defined $self->{conf};
64 5         9 $self->{_TREE_POS} = $self->{_TREE_ROOT};
65              
66             #set the separators
67 5         13 $self->_set_separator;
68             }
69              
70             #public method, takes the X12 and Cf file name as input.
71             #loads the config file and sets the separators.
72             sub parsefile {
73 3     3 1 16 my $self = shift;
74 3         12 my %params = @_;
75 3         21 $self->{file} = $params{file};
76 3         7 $self->{conf} = $params{conf};
77              
78             #chose the handle just in case this method is being called the second time
79             #without closing the file
80 3 100       13 if ( defined $self->{_FILE_HANDLE} ) {
81 1         1 close( $self->{_FILE_HANDLE} );
82 1         2 $self->{_FILE_CLOSED} = 1;
83             }
84 3 50       169 open( my $handle, "$self->{file}" )
85             || die "error: cannot open file $self->{file}\n";
86 3         16 $self->parse( handle => $handle, conf => $self->{conf} );
87             }
88              
89             #close the file
90             sub closefile {
91 2     2 1 203 my $self = shift;
92 2 50       6 if ( defined $self->{_FILE_HANDLE} ) {
93 2         32 close( $self->{_FILE_HANDLE} );
94 2         6 $self->{_FILE_CLOSED} = 1;
95             }
96             }
97              
98             #private method. sets the separators.
99             sub _set_separator {
100 5     5   6 my $self = shift;
101 5         6 my $isa = undef;
102 5 50       130 if ( read( $self->{_FILE_HANDLE}, $isa, 108 ) != 108 ) {
103 0         0 close( $self->{_FILE_HANDLE} );
104 0         0 $self->{_FILE_CLOSED} = 1;
105 0         0 die "error: invalid file format $self->{file}\n";
106             }
107              
108             #set the segment terminator
109 5         13 my $terminator = substr( $isa, 106, 2 );
110 5 50       30 if ( $terminator =~ /\r\n/ ) {
    50          
111 0         0 $self->{_SEGMENT_SEPARATOR} = substr( $isa, 105, 3 );
112             }
113             elsif ( $terminator =~ /^\n/ ) {
114 0         0 $self->{_SEGMENT_SEPARATOR} = substr( $isa, 105, 2 );
115             }
116             else {
117 5         7 $self->{_SEGMENT_SEPARATOR} = substr( $isa, 105, 1 );
118             }
119              
120             #set the element separator
121 5         10 $self->{_ELEMENT_SEPARATOR} = substr( $isa, 3, 1 );
122              
123             #set the sub element separator
124 5         8 $self->{_SUBELEMENT_SEPARATOR} = substr( $isa, 104, 1 );
125 5         620 seek( $self->{_FILE_HANDLE}, -108, 1 );
126             }
127              
128             #public method. gets the next loop.
129             sub get_next_loop {
130 8     8 1 643 my $self = shift;
131 8 50       538 if ( defined $self->{_NEXT_LOOP} ) {
132 0         0 my $loop = $self->{_NEXT_LOOP};
133 0         0 $self->{_NEXT_LOOP} = undef;
134 0         0 return $loop;
135             }
136             else {
137 8         16 return $self->_get_next_loop();
138             }
139             }
140              
141             sub get_next_pos_loop {
142 4     4 1 509 my $self = shift;
143 4         5 my $loop = undef;
144 4 50       10 if ( defined $self->{_NEXT_LOOP} ) {
145 0         0 $loop = $self->{_NEXT_LOOP};
146 0         0 $self->{_NEXT_LOOP} = undef;
147 0         0 return ( $., $loop );
148             }
149             else {
150 4         5 $loop = $self->_get_next_loop();
151 4 50       8 if ( defined $loop ) {
152 4         16 return ( $., $loop );
153             }
154             else {
155 0         0 return;
156             }
157             }
158             }
159              
160             sub get_next_pos_level_loop {
161 17     17 1 540 my $self = shift;
162 17         18 my $loop = undef;
163 17 100       35 if ( defined $self->{_NEXT_LOOP} ) {
164 11         14 $loop = $self->{_NEXT_LOOP};
165 11         12 $self->{_NEXT_LOOP} = undef;
166 11         31 return ( $., $self->{_TREE_POS}->get_depth(), $loop );
167             }
168             else {
169 6         13 $loop = $self->_get_next_loop();
170 6 100       16 if ( defined $loop ) {
171 5         17 return ( $., $self->{_TREE_POS}->get_depth(), $loop );
172             }
173             else {
174 1         5 return;
175             }
176             }
177             }
178              
179             #private method. does the hard lifting.
180             sub _get_next_loop {
181 30     30   31 my $self = shift;
182 30         31 my ( $segment, $file_handle, $node, $loop, @element );
183 30         78 local $/;
184 30         54 $/ = $self->{_SEGMENT_SEPARATOR};
185 30         43 $file_handle = $self->{_FILE_HANDLE};
186 30         31 $node = $self->{_TREE_POS};
187 30         42 $self->{_LOOP} = [];
188 30 100       76 if ( defined $self->{_NEXT_SEGMENT} ) {
189 27         26 push( @{ $self->{_LOOP} }, $self->{_NEXT_SEGMENT} );
  27         56  
190 27         34 $self->{_NEXT_SEGMENT} = undef;
191             }
192 30 100       59 if ( defined $self->{_FILE_CLOSED} ) {
193 1         4 return undef;
194             }
195 29         102 while ( $segment = <$file_handle> ) {
196 60         62 chomp($segment);
197 60         307 @element = split( /\Q$self->{_ELEMENT_SEPARATOR}\E/, $segment );
198 60         155 $loop = $self->_check_child_match( $node, \@element );
199 60 100       110 if ( defined $loop ) {
200 7         9 $self->{_NEXT_SEGMENT} = $segment;
201 7         35 return $loop;
202             }
203 53         87 $loop = $self->_check_parent_match( $node, \@element );
204 53 100       96 if ( defined $loop ) {
205 21         29 $self->{_NEXT_SEGMENT} = $segment;
206 21         78 return $loop;
207             }
208 32         29 push( @{ $self->{_LOOP} }, $segment );
  32         110  
209             }
210 1         14 close($file_handle);
211 1         2 $self->{_FILE_CLOSED} = 1;
212 1         5 return undef;
213             }
214              
215             #private method. check if any of the child loops match
216             sub _check_child_match {
217 60     60   85 my ( $self, $node, $elements ) = @_;
218 60         150 for ( my $i = 0 ; $i < $node->get_child_count() ; $i++ ) {
219 23         50 my $child = $node->get_child($i);
220 23 100       52 if ( $child->is_loop_start($elements) ) {
221 7         9 $self->{_TREE_POS} = $child;
222 7         18 return $child->get_name();
223             }
224             }
225 53         77 return undef;
226             }
227              
228             #private method. check if any of the parent loops match
229             sub _check_parent_match {
230 98     98   113 my ( $self, $node, $elements ) = @_;
231 98         188 my $parent = $node->get_parent();
232 98 100       180 if ( !defined $parent ) { return undef; }
  32         62  
233 66         134 for ( my $i = 0 ; $i < $parent->get_child_count() ; $i++ ) {
234 387         742 my $child = $parent->get_child($i);
235 387 100       760 if ( $child->is_loop_start($elements) ) {
236 21         25 $self->{_TREE_POS} = $child;
237 21         45 return $child->get_name();
238             }
239             }
240 45         101 $self->_check_parent_match( $parent, $elements );
241             }
242              
243             #get the segments in the loop
244             sub get_loop_segments {
245 12     12 1 14 my $self = shift;
246 12         16 my $loop = $self->_get_next_loop();
247 12         17 $self->{_NEXT_LOOP} = $loop;
248 12         13 return @{ $self->{_LOOP} };
  12         36  
249             }
250              
251             sub get_segment_separator {
252 0     0 1 0 my $self = shift;
253 0         0 return $self->{_SEGMENT_SEPARATOR};
254             }
255              
256             sub get_element_separator {
257 0     0 1 0 my $self = shift;
258 0         0 return $self->{_ELEMENT_SEPARATOR};
259             }
260              
261             sub get_subelement_separator {
262 0     0 1 0 my $self = shift;
263 0         0 return $self->{_SUBELEMENT_SEPARATOR};
264             }
265              
266             sub print_tree {
267 0     0 1 0 my $self = shift;
268 0         0 my ( $pad, $index, $segment );
269 0         0 while ( my ( $pos, $level, $loop ) = $self->get_next_pos_level_loop ) {
270 0         0 $pad = ' |' x $level;
271 0         0 print " $pad--$loop\n";
272 0         0 $pad = ' |' x ( $level + 1 );
273 0         0 my @loop = $self->get_loop_segments;
274 0         0 foreach $segment (@loop) {
275 0         0 $index = sprintf( "%+7s", $pos++ );
276 0         0 print "$index$pad-- $segment\n";
277             }
278             }
279             }
280              
281             #private method only called for tests
282             sub _print_tree {
283 1     1   6 my $self = shift;
284 1         2 my ( $pad, $index, $segment, $tree );
285 1         4 while ( my ( $pos, $level, $loop ) = $self->get_next_pos_level_loop ) {
286 12         15 $pad = ' |' x $level;
287 12         23 $tree .= " $pad--$loop\n";
288 12         16 $pad = ' |' x ( $level + 1 );
289 12         38 my @loop = $self->get_loop_segments;
290 12         22 foreach $segment (@loop) {
291 32         61 $index = sprintf( "%+7s", $pos++ );
292 32         95 $tree .= "$index$pad-- $segment\n";
293             }
294             }
295 1         9 return $tree;
296             }
297             1;
298             __END__