File Coverage

blib/lib/PICA/PlainParser.pm
Criterion Covered Total %
statement 128 144 88.8
branch 68 94 72.3
condition 18 30 60.0
subroutine 14 15 93.3
pod 9 9 100.0
total 237 292 81.1


line stmt bran cond sub pod time code
1             package PICA::PlainParser;
2             {
3             $PICA::PlainParser::VERSION = '0.585';
4             }
5             #ABSTRACT: Parse normalized PICA+
6 13     13   72 use strict;
  13         21  
  13         498  
7              
8              
9 13     13   2252 use PICA::Field;
  13         26  
  13         536  
10 13     13   75 use PICA::Record;
  13         22  
  13         588  
11 13     13   64 use Carp qw(croak);
  13         24  
  13         24470  
12              
13              
14             sub new {
15 28     28 1 82 my ($class, %params) = @_;
16 28   33     131 $class = ref $class || $class;
17              
18 28 100 100     986 my $self = bless {
    100 50        
    50 50        
    50          
    100          
19             field_handler => defined $params{Field} ? $params{Field} : undef,
20             record_handler => defined $params{Record} ? $params{Record} : undef,
21              
22             broken_field_handler => defined $params{FieldError} ? $params{FieldError} : undef,
23             broken_record_handler => defined $params{RecordError} ? $params{RecordError} : undef,
24              
25             proceed => $params{Proceed} ? $params{Proceed} : 0,
26             limit => ($params{Limit} || 0) * 1,
27             offset => ($params{Offset} || 0) * 1,
28              
29             record => undef,
30             broken => undef, # broken record
31              
32             read_records => [],
33             'strict' => $params{strict} || 0,
34             filename => "",
35             fields => [],
36             read_counter => 0,
37             active => 0,
38             }, $class;
39              
40 28         128 return $self;
41             }
42              
43              
44             sub parsefile {
45 22     22 1 44 my ($self, $file) = @_;
46              
47 22 100       150 if ( ref($file) eq 'GLOB' ) {
    100          
48 5         21 $self->{filehandle} = $file;
49 5         16 $self->{filename} = "";
50             } elsif ( UNIVERSAL::isa( $file, 'IO::Handle' ) ) {
51 5         26 $self->{filehandle} = $file;
52 5         15 $self->{filename} = "";
53             } else {
54 12         29 $self->{filename} = $file;
55              
56 12         27 my $fh = $file;
57 12 50       48 $fh = "zcat $fh |" if $fh =~ /\.gz$/;
58 12 50       36 $fh = "unzip -p $fh |" if $fh =~ /\.zip$/;
59              
60 12 50       112 $self->{filehandle} = IO::File->new($file, '<:utf8')
61             or croak("failed to open file $file");
62             }
63              
64 22         1627 PICA::Parser::enable_binmode_encoding( $self->{filehandle} );
65              
66 22 50       173 if ( not $self->{proceed} ) {
67 22         42 $self->{read_counter} = 0;
68 22         57 $self->{read_records} = [];
69             }
70              
71 22         181 $self->{active} = 0;
72 22         33 $self->{record} = undef;
73              
74 22         40 my $dumpformat = 0;
75 22         598 my $line = readline( $self->{filehandle} );
76 22 100       109 if ($line =~ /\x1E/) { # dumpformat useds \x1E instead of newlines
77              
78 3         11 my $EOL = $/;
79 3         12 $/ = chr(0x1E);
80 3         7 my $id = "";
81              
82 3         329 my @linebuf = split( /\x1E/, $line );
83            
84 3         34 do {
85 224 50       591 last if ($self->finished());
86 224 100       545 if (@linebuf) {
87 221         501 $line = shift @linebuf;
88 221 100 66     1751 if (defined $line and not @linebuf) {
89 3         468 $line .= readline( $self->{filehandle} );
90             }
91             } else {
92 3         35 $line = readline( $self->{filehandle} );
93             }
94 224 100       714 if ( defined $line ) {
95 221         721 $line =~ /^\x1D?([^\s]*)/;
96 221 100       664 if (PICA::Field::parse_pp_tag($1)) {
97 208         487 $self->_parseline($line);
98             } else {
99 13 100       62 if ( "$id" ne "$1" ) {
100 6         20 $self->_parseline(""); # next record
101             }
102 13         72 $id = $1;
103             }
104             }
105             } while(defined $line);
106              
107 3         16 $/ = $EOL;
108              
109             } else {
110 19   66     146 while ( defined $line and not $self->finished ) {
111 3560         7726 $self->_parseline($line);
112 3560         16901 $line = readline( $self->{filehandle} );
113             };
114             }
115              
116 22 50       84 $self->handle_record() unless $self->finished(); # handle last record
117              
118 22         100 $self;
119             }
120              
121              
122             sub parsedata {
123 10     10 1 30 my ($self, $data, $additional) = @_;
124              
125 10         24 $self->{active} = 0;
126 10         21 $self->{record} = undef;
127              
128 10 100       32 if ( ! $self->{proceed} ) {
129 8         17 $self->{read_counter} = 0;
130 8         22 $self->{read_records} = [];
131             }
132              
133 10 100       189 if ( ref($data) eq 'CODE' ) {
    100          
134 1         5 my $chunk = &$data();
135 1         54 while(defined $chunk) {
136 27         249 $self->_parsedata($chunk);
137 27         84 $chunk = &$data();
138             }
139             } elsif( UNIVERSAL::isa( $data, "PICA::Record" ) ) {
140             # re-parse the record (could obviously be speed up by dropping tests)
141 1         6 foreach ( $data->fields ) {
142 26         87 $self->_parseline( $_->string );
143             }
144             } else {
145 8         33 $self->_parsedata($data);
146             }
147              
148 10         62 $self->handle_record(); # handle last record
149              
150 10         46 $self;
151             }
152              
153              
154              
155             sub records {
156 4     4 1 10 my $self = shift;
157 4         7 return @{ $self->{read_records} };
  4         30  
158             }
159              
160              
161             sub counter {
162 3305     3305 1 3951 my $self = shift;
163 3305         20068 return $self->{read_counter};
164             }
165              
166              
167             sub finished {
168 3806     3806 1 4980 my $self = shift;
169 3806   66     13104 return $self->{limit} && $self->counter() >= $self->{limit};
170             }
171              
172              
173             sub _parsedata {
174 35     35   98 my ($self, $data) = @_;
175              
176 35         59 my @lines;
177              
178 35 100       117 if (ref(\$data) eq 'SCALAR') {
    50          
179 34 50       358 @lines = $data eq "\n" ? ('') : split "\n", $data;
180             } elsif (ref($data) eq 'ARRAY') {
181 1         2 @lines = @{$data};
  1         14  
182             } else {
183 0         0 croak("Got " . ref(\$data) . " when parsing PICA+ while expecting SCALAR or ARRAY");
184             }
185              
186 35         104 foreach my $line (@lines) {
187 243         646 $self->_parseline($line);
188             }
189             }
190              
191              
192             sub _parseline {
193 4043     4043   6775 my ($self, $line) = @_;
194 4043         11765 chomp $line; # remove newline if present
195              
196             # start of record marker
197 4043 100 66     51158 if ( $line eq "\x1D" or (not $self->{strict} and $line =~ /^\s*$|^#|^SET/) ) {
      66        
198 27 100 66     112 $self->handle_record() if $self->{active} and @{$self->{fields}};
  4         33  
199             } else {
200 4016         6518 $line =~ s/^\x1D//;
201 4016         4895 my $field = eval { PICA::Field->parse($line); };
  4016         12433  
202 4016 50       13740 if ($@) {
    100          
203 0         0 $@ =~ s/ at .*\n//; # remove line number
204 0         0 $field = $self->broken_field( $@, $line );
205             } elsif ($self->{field_handler}) {
206 3102         8400 $field = $self->{field_handler}( $field );
207             }
208 4016 100       13621 if ( UNIVERSAL::isa( $field, 'PICA::Field' ) ) {
    50          
209 914         1285 push (@{$self->{fields}}, $field);
  914         2643  
210             } elsif ( defined $field ) {
211 0 0       0 $self->{broken} = $field unless defined $self->{broken};
212             }
213             }
214 4043         13585 $self->{active} = 1;
215             }
216              
217              
218             sub broken_field {
219 0     0 1 0 my ($self, $msg, $line) = @_;
220 0 0       0 if ($self->{broken_field_handler}) {
221 0         0 return $self->{broken_field_handler}( $msg, $line );
222             }
223 0 0       0 $msg = "$msg in line \"$line\"" if defined $line;
224 0         0 print STDERR "$msg\n";
225             # TODO: count/collect errors
226 0         0 return;
227             }
228              
229              
230             sub broken_record {
231 6     6 1 18 my ($self, $msg, $record) = @_;
232 6 50       19 if ($self->{broken_record_handler}) {
233 0         0 return $self->{broken_record_handler}( $msg, $record );
234             }
235 6 50 33     47 return if UNIVERSAL::isa( $record, 'PICA::Record' ) && $record->empty;
236 0 0       0 print STDERR "$msg\n" if defined $msg;
237 0         0 return;
238             }
239              
240              
241             sub handle_record {
242 36     36 1 95 my $self = shift;
243              
244 36         78 $self->{read_counter}++;
245              
246 36         66 my ($record, $broken);
247              
248             # $self->{broken} = "empty record"
249             # unless defined $self->{broken} or @{$self->{fields}} > 0;
250              
251 36 50       114 if ( $self->{broken} ) {
252 0         0 $broken = $self->{broken};
253             } else {
254 36         68 $record = PICA::Record->new( @{$self->{fields}} );
  36         341  
255             }
256 36         98 $self->{fields} = [];
257 36         122 $self->{broken} = undef;
258              
259             # TODO: fix this
260             # return if ($self->{offset} && $self->{read_counter} < $self->{offset});
261              
262 36 50       109 if (not defined $broken) {
263 36 100       124 if ($self->{record_handler}) {
264 17 50       71 if (UNIVERSAL::isa( $self->{record_handler}, 'PICA::Writer') ) {
265 0         0 $self->{record_handler}->write( $record );
266             #$record = TODO allow here!
267             } else {
268 17         85 $record = $self->{record_handler}( $record );
269 17 100       273 $record = undef if $record =~ /^-?\d+$/;
270             }
271             }
272 36 100       111 if (defined $record) {
273 31 50       144 if ( UNIVERSAL::isa( $record, 'PICA::Record' ) ) {
274 31 100       149 $broken = "empty record" if $record->empty;
275             } else {
276 0         0 $broken = $record;
277             }
278             }
279             }
280              
281 36 100       204 if ( defined $broken ) {
    100          
282 6         21 $self->broken_record( $broken, $record );
283             } elsif ( defined $record ) {
284 25         36 push @{ $self->{read_records} }, $record;
  25         100  
285             }
286             }
287              
288             1;
289              
290             __END__