File Coverage

blib/lib/Biblio/SICI.pm
Criterion Covered Total %
statement 130 164 79.2
branch 44 100 44.0
condition 30 60 50.0
subroutine 16 16 100.0
pod 6 6 100.0
total 226 346 65.3


line stmt bran cond sub pod time code
1              
2             package Biblio::SICI;
3             {
4             $Biblio::SICI::VERSION = '0.04';
5             }
6              
7             # ABSTRACT: Provides methods for assembling, parsing, manipulating and serialising SICIs
8              
9 3     3   83658 use strict;
  3         8  
  3         126  
10 3     3   19 use warnings;
  3         6  
  3         94  
11 3     3   99 use 5.010001;
  3         11  
  3         125  
12              
13 3     3   12238 use Moo;
  3         70271  
  3         20  
14 3     3   9886 use Sub::Quote;
  3         15081  
  3         237  
15              
16 3     3   2296 use Biblio::SICI::ItemSegment;
  3         11  
  3         161  
17 3     3   2253 use Biblio::SICI::ContributionSegment;
  3         13  
  3         136  
18 3     3   2192 use Biblio::SICI::ControlSegment;
  3         10  
  3         154  
19              
20 3     3   25 use Biblio::SICI::Util qw( calculate_check_char );
  3         7  
  3         7257  
21              
22              
23             has 'item' => (
24             is => 'ro',
25             lazy => 1,
26             isa => quote_sub(q{ die unless ( defined $_[0] and $_[0]->isa('Biblio::SICI::ItemSegment') ) }),
27             builder =>
28             quote_sub(q{ my ($self) = @_; return Biblio::SICI::ItemSegment->new( _sici => $self ); }),
29             init_arg => undef,
30             );
31              
32              
33             has 'contribution' => (
34             is => 'ro',
35             lazy => 1,
36             isa => quote_sub(
37             q{ die unless ( defined $_[0] and $_[0]->isa('Biblio::SICI::ContributionSegment') ) }),
38             builder => quote_sub(
39             q{ my ($self) = @_; return Biblio::SICI::ContributionSegment->new( _sici => $self ); }),
40             init_arg => undef,
41             );
42              
43              
44             has 'control' => (
45             is => 'ro',
46             lazy => 1,
47             isa => quote_sub(
48             q{ my ($val) = @_; die unless ( defined $val and $val->isa('Biblio::SICI::ControlSegment') ) }
49             ),
50             builder => quote_sub(
51             q{ my ($self) = @_; return Biblio::SICI::ControlSegment->new( _sici => $self ); }),
52             init_arg => undef,
53             );
54              
55              
56             has 'mode' => (
57             is => 'rw',
58             isa => quote_sub(q{ my ($val) = @_; die unless ( $val eq 'strict' or $val eq 'lax' ) }),
59             required => 1,
60             coerce => sub {
61             my ($val) = @_;
62             $val = join( '', split( " ", lc($val) ) );
63             return $val if ( $val eq 'strict' or $val eq 'lax' );
64             return 'lax';
65             },
66             default => quote_sub(q{ "lax" }),
67             );
68              
69              
70             has 'parsedString' => ( is => 'rwp', init_arg => undef, );
71              
72              
73             sub parse {
74 58     58 1 1159 my ( $self, $string ) = @_;
75 58 50       1206 my $strictMode = $self->mode() eq 'strict' ? 1 : 0;
76              
77 58 50       1162 if ( defined $string ) {
78 58         153 $string =~ s/\r/ /go;
79 58         94 $string =~ s/\n/ /go;
80 58         211 $string = join( '', split( " ", $string ) );
81             }
82              
83 58 50       141 unless ($string) {
84 0 0       0 $strictMode ? die 'no string to parse' : return ( undef, undef, ['no string to parse'] );
85             }
86 58         180 $self->_set_parsedString($string);
87              
88 58         80 my $checkChar = '';
89 58 50       293 if ( $string =~ /;([0-9])(?:-.)?\Z/ ) {
90 58 50       157 if ( "$1" ne "2" ) {
91 0 0       0 $strictMode
92             ? die 'unsupported SICI version'
93             : return ( undef, undef, ['unsupported SICI version'] );
94             }
95             else {
96 58         1175 $self->control()->version(2);
97 58 50       472 if ( $string =~ s/;2-(.)\Z// ) {
98 58         126 $checkChar = $1;
99             }
100             }
101             }
102              
103 58         108 my $parserProblems = [];
104              
105 58         576 my @chars = split( //, $string );
106 58         149 my $tmp = '';
107 58   66     320 while ( exists( $chars[0] ) and $chars[0] !~ /[(<]/ ) {
108 522         2009 $tmp .= shift @chars;
109             }
110              
111 58 50 33     246 if ( $tmp and exists( $chars[0] ) ) {
112 58 50       144 if ( $chars[0] eq '(' ) {
    0          
113              
114             # warn 'ISSN candidate: ' . $tmp;
115 58         1172 $self->item()->issn($tmp);
116 58         272 shift @chars;
117             }
118             elsif ( $chars[0] eq '<' ) {
119 0         0 push @{$parserProblems}, "item information missing";
  0         0  
120              
121             # warn 'Missing item info';
122             # warn 'Enumeration candidate: ' . $tmp;
123 0 0       0 if ( $tmp =~ /\A([A-Z0-9\/]+):([A-Z0-9\/]+)(?::([+*]))?\Z/ ) {
    0          
124 0         0 $self->item()->volume($1);
125 0         0 $self->item()->issue($2);
126 0 0       0 $self->item()->supplOrIdx($3) if $3;
127             }
128             elsif ($tmp) {
129 0         0 $self->item()->enumeration($tmp);
130             }
131 0         0 shift @chars;
132 0         0 goto CONTRIB;
133             }
134             else {
135 0 0       0 $strictMode ? die 'unparsable string' : return ( undef, undef, ['unparsable string'] );
136             }
137             } ## end if ( $tmp and exists( ...))
138             else {
139 0 0       0 $strictMode ? die 'unparsable string' : return ( undef, undef, ['unparsable string'] );
140             }
141              
142 58         121 $tmp = '';
143 58   66     288 while ( exists( $chars[0] ) and $chars[0] ne ')' ) {
144 379         1319 $tmp .= shift @chars;
145             }
146 58 100 66     406 if ( $tmp and exists( $chars[0] ) and $chars[0] eq ')' ) {
    50 66        
      33        
147              
148             # warn 'Chronology candidate: ' . $tmp;
149 57         1220 $self->item()->chronology($tmp);
150 57         228 shift @chars;
151             }
152             elsif ( exists( $chars[0] ) and $chars[0] eq ')' ) {
153 1         1 shift @chars;
154             }
155             else {
156 0 0       0 $strictMode ? die 'unparsable string' : return ( undef, undef, ['unparsable string'] );
157             }
158              
159 58         167 $tmp = '';
160 58   66     274 while ( exists( $chars[0] ) and $chars[0] ne '<' ) {
161 218         812 $tmp .= shift @chars;
162             }
163 58 100 66     379 if ( $tmp and exists( $chars[0] ) and $chars[0] eq '<' ) {
    50 66        
      33        
164              
165             # warn 'Enumeration candidate: ' . $tmp;
166 55 100       258 if ( $tmp =~ /\A([A-Z0-9\/]+):([A-Z0-9\/]+)(?::([+*]))?\Z/ ) {
    50          
167 39         802 $self->item()->volume($1);
168 39         876 $self->item()->issue($2);
169 39 50       217 $self->item()->supplOrIdx($3) if $3;
170             }
171             elsif ($tmp) {
172 16         307 $self->item()->enumeration($tmp);
173             }
174 55         117 shift @chars;
175             }
176             elsif ( exists( $chars[0] ) and $chars[0] eq '<' ) {
177 3         4 shift @chars;
178             }
179             else {
180 0 0       0 $strictMode ? die 'unparsable string' : return ( undef, undef, ['unparsable string'] );
181             }
182              
183 58         87 CONTRIB:
184             $tmp = '';
185 58   66     350 while ( exists( $chars[0] ) and $chars[0] ne '>' ) {
186 278         994 $tmp .= shift @chars;
187             }
188 58 100 66     381 if ( $tmp and exists( $chars[0] ) and $chars[0] eq '>' ) {
    50 66        
      33        
189              
190             # warn 'Contribution candidate: ' . $tmp;
191 28 100       224 if ( $tmp =~ /\A::(.+)\Z/ ) {
    100          
    50          
192 2         22 $self->contribution()->localNumber($1);
193             }
194             elsif ( $tmp =~ /\A:([^:]+)(?::(.+))?\Z/ ) {
195 1         19 $self->contribution()->titleCode($1);
196 1 50       9 $self->contribution()->localNumber($2) if $2;
197             }
198             elsif ( $tmp =~ /\A([^:]+):([^:]+)(?::(.+))?\Z/ ) {
199 25         505 $self->contribution()->location($1);
200 25         539 $self->contribution()->titleCode($2);
201 25 100       184 $self->contribution()->localNumber($3) if $3;
202             }
203             else {
204 0         0 $self->contribution()->location($tmp);
205             }
206 28         57 shift @chars;
207             }
208             elsif ( exists( $chars[0] ) and $chars[0] eq '>' ) {
209 30         34 shift @chars;
210             }
211             else {
212 0 0       0 $strictMode ? die 'unparsable string' : return ( undef, undef, ['unparsable string'] );
213             }
214              
215 58         104 my $csi = '';
216 58 50       147 if ( exists( $chars[0] ) ) {
217 58         86 $csi = shift @chars;
218             }
219              
220 58 50 33     264 if ( exists( $chars[0] ) and $chars[0] eq '.' ) {
    0          
221 58         105 shift @chars;
222             }
223             elsif ( exists( $chars[0] ) ) {
224 0         0 shift(@chars) while ( $chars[0] ne '.' );
225             }
226              
227 58 50       160 if ( exists( $chars[0] ) ) {
228 58         1139 $self->control()->dpi( shift @chars );
229             }
230              
231 58 50 33     422 if ( exists( $chars[0] ) and $chars[0] eq '.' ) {
    0          
232 58         78 shift @chars;
233             }
234             elsif ( exists( $chars[0] ) ) {
235 0         0 shift(@chars) while ( $chars[0] ne '.' );
236             }
237              
238 58 50 33     267 if ( exists( $chars[0] ) and exists( $chars[1] ) ) {
239 58         1104 $self->control()->mfi( join( '', splice( @chars, 0, 2 ) ) );
240             }
241              
242 58         341 my $isValid = $self->is_valid();
243 58 50 33     156 if ( $strictMode && !$isValid ) {
244 0         0 die 'parsing failed: invalid SICI';
245             }
246              
247 58 50       145 if ( $checkChar ne $self->checkchar() ) {
248 0         0 push @{$parserProblems}, "wrong check char; was '$checkChar', should have been '$1'";
  0         0  
249             }
250 58 50       231 if ( $checkChar !~ /\A[0-9A-Z#]\Z/ ) {
251 0         0 push @{$parserProblems}, 'invalid original check char';
  0         0  
252             }
253              
254 58 50       1253 if ( $self->control()->csi() ne $csi ) {
255 0         0 push @{$parserProblems}, 'wrong csi in string input';
  0         0  
256             }
257              
258 58 50       189 my $roundTrip = ( $self->parsedString() eq $self->to_string() ? 1 : 0 );
259 58         216 return ( $isValid, $roundTrip, $parserProblems );
260             } ## end sub parse
261              
262              
263             sub to_string {
264 121     121 1 43585 my $self = shift;
265              
266 121         245 my $str = $self->_to_string();
267 121         333 my $cs = calculate_check_char($str);
268              
269 121         540 return $str . $cs;
270             }
271              
272             sub _to_string {
273 179     179   185 my $self = shift;
274              
275 179         3713 my $item = $self->item()->to_string();
276 179         3482 my $contrib = $self->contribution()->to_string();
277 179         3441 my $control = $self->control()->to_string();
278              
279 179         10381 return sprintf( '%s<%s>%s-', $item, $contrib, $control );
280             }
281              
282              
283             sub checkchar {
284 58     58 1 82 my $self = shift;
285              
286 58         141 my $siciAsString = $self->_to_string();
287              
288 58         242 return calculate_check_char($siciAsString);
289             }
290              
291              
292             sub reset {
293 58     58 1 127 my $self = shift;
294 58         1390 $self->item()->reset();
295 58         1125 $self->contribution()->reset();
296 58         1164 $self->control()->reset();
297 58         158 return;
298             }
299              
300              
301             sub is_valid {
302 58     58 1 83 my $self = shift;
303              
304 58         1107 my $itemIsValid = $self->item()->is_valid();
305 58         1160 my $contribIsValid = $self->contribution()->is_valid();
306 58         1118 my $controlIsValid = $self->control()->is_valid();
307              
308 58 50 33     354 if ( $itemIsValid && $contribIsValid && $controlIsValid ) {
      33        
309 58         127 return 1;
310             }
311              
312 0         0 return 0;
313             }
314              
315              
316             sub list_problems {
317 63     63 1 425 my $self = shift;
318              
319 63         93 my $hasProblems = 0;
320 63         150 my %problems = ();
321 63 50       1374 if ( not $self->item()->is_valid() ) {
322 0         0 $hasProblems++;
323 0         0 $problems{'item'} = { $self->item()->list_problems() };
324             }
325 63 50       1263 if ( not $self->contribution()->is_valid() ) {
326 0         0 $hasProblems++;
327 0         0 $problems{'contribution'} = { $self->contribution()->list_problems() };
328             }
329 63 50       1208 if ( not $self->control()->is_valid() ) {
330 0         0 $hasProblems++;
331 0         0 $problems{'control'} = { $self->control()->list_problems() };
332             }
333              
334 63 50       143 if ($hasProblems) {
335 0         0 return %problems;
336             }
337 63         310 return;
338             }
339              
340             1;
341              
342             __END__