File Coverage

blib/lib/Business/SEDOL.pm
Criterion Covered Total %
statement 49 49 100.0
branch 22 22 100.0
condition n/a
subroutine 9 9 100.0
pod 6 6 100.0
total 86 86 100.0


line stmt bran cond sub pod time code
1             package Business::SEDOL;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Business::SEDOL - Verify Stock Exchange Daily Official List Numbers
8              
9             =head1 SYNOPSIS
10              
11             use Business::SEDOL;
12             $sdl = Business::SEDOL->new('0325015');
13             print "Looks good.\n" if $sdl->is_valid;
14              
15             $sdl = Business::SEDOL->new('0123457');
16             $chk = $sdl->check_digit;
17             $sdl->sedol($sdl->sedol.$chk);
18             print $sdl->is_valid ? "Looks good." : "Invalid: ", $sdl->error, "\n";
19              
20             =head1 DESCRIPTION
21              
22             This module verifies SEDOLs, which are British securities identification
23             codes. This module cannot tell if a SEDOL references a real security, but it
24             can tell you if the given SEDOL is properly formatted. It handles both the
25             old-style SEDOLs (SEDOLs issued prior to 26 January 2004) and new-style SEDOLs.
26              
27             =cut
28              
29 4     4   15302 use strict;
  4         27  
  4         145  
30 4     4   21 use vars qw($VERSION $ERROR);
  4         18  
  4         2916  
31              
32             $VERSION = '2.02';
33              
34             # Global variables used by many.
35             # SEDOLs can basically be comprised of 0..9 and B..Z excluding vowels.
36             my %valid_chars = map {$_ => $a++} 0..9, 'A'..'Z';
37             delete @valid_chars{qw/A E I O U/};
38             my $valid_alpha = join('',grep /\w/, sort keys %valid_chars);
39             my @weights = (1, 3, 1, 7, 3, 9, 1);
40              
41             =head1 METHODS
42              
43             =over 4
44              
45             =item new([SEDOL_NUMBER])
46              
47             The new constructor optionally takes the SEDOL number.
48              
49             =cut
50             sub new {
51 52     52 1 2497 my ($class, $sedol) = @_;
52 52         166 bless \$sedol, $class;
53             }
54              
55             =item sedol([SEDOL_NUMBER])
56              
57             If no argument is given to this method, it will return the current SEDOL
58             number. If an argument is provided, it will set the SEDOL number and then
59             return the SEDOL number.
60              
61             =cut
62             sub sedol {
63 266     266 1 401 my $self = shift;
64 266 100       484 $$self = shift if @_;
65 266         608 return $$self;
66             }
67              
68             =item series()
69              
70             Returns the series number of the SEDOL.
71              
72             =cut
73             sub series {
74 3     3 1 5 my $self = shift;
75 3         6 return substr($self->sedol, 0, 1);
76             }
77              
78             sub _check_format {
79 145     145   196 my $val = shift;
80              
81 145         191 $ERROR = undef;
82              
83 145 100       253 if (length($val) != 7) {
84 12         17 $ERROR = "SEDOLs must be 7 characters long.";
85 12         38 return '';
86             }
87              
88 133 100       377 if ($val =~ /^\d/) {
89             # assume old-style
90 106 100       222 if ($val =~ /\D/) {
91 22         47 $ERROR = "Old-style SEDOLs must contain only numerals.";
92 22         73 return '';
93             }
94             } else {
95             # assume new-style
96 27 100       213 if ($val !~ /^[$valid_alpha]/o) {
    100          
    100          
97 5         10 $ERROR = "New-style SEDOL must have alphabetic first character.";
98 5         21 return '';
99             } elsif ($val !~ /^.[\d$valid_alpha]{5}/o) {
100 3         16 $ERROR = "New-style SEDOL must have alphanumeric characters 2-6.";
101 3         14 return '';
102             } elsif ($val =~ /\D$/) {
103 1         3 $ERROR = "SEDOL checkdigit (character 7) must be numeric.";
104 1         6 return '';
105             }
106             }
107 102         194 return 1;
108             }
109              
110             =item is_valid()
111              
112             Returns true if the checksum of the SEDOL is correct otherwise it returns
113             false and $Business::SEDOL::ERROR will contain a description of the problem.
114              
115             =cut
116             sub is_valid {
117 72     72 1 758 my $self = shift;
118 72         122 my $val = $self->sedol;
119              
120 72 100       123 return '' unless _check_format($val);
121              
122 39         77 my $c = $self->check_digit;
123 39 100       79 if (substr($self->sedol, -1, 1) eq $c) {
124 12         39 return 1;
125             } else {
126 27         57 $ERROR = "Check digit not correct. Expected $c.";
127 27         85 return '';
128             }
129             }
130              
131             =item error()
132              
133             If the SEDOL object is not valid (! is_valid()) it returns the reason it is
134             not valid. Otherwise returns undef.
135              
136             =cut
137             sub error {
138 32     32 1 81 shift->is_valid;
139 32         88 return $ERROR;
140             }
141              
142             =item check_digit()
143              
144             This method returns the checksum of the object. This method ignores the check
145             digit of the object's SEDOL number instead recalculating the check_digit each
146             time. If the check digit cannot be calculated, undef is returned and
147             $Business::SEDOL::ERROR contains the reason.
148              
149             =cut
150             sub check_digit {
151 73     73 1 163 my $self = shift;
152 73         124 my $sedol = $self->sedol;
153 73 100       157 $sedol .= "0" if length($sedol) == 6;
154 73 100       117 return unless _check_format($sedol);
155              
156 63         106 my @val = split //, $self->sedol;
157 63         104 my $sum = 0;
158 63         118 for (0..5) {
159 378         603 $sum += $valid_chars{$val[$_]} * $weights[$_];
160             }
161 63         193 return (10 - $sum % 10) % 10;
162             }
163              
164             1;
165             __END__