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   25167 use strict;
  4         8  
  4         167  
30 4     4   23 use vars qw($VERSION $ERROR);
  4         7  
  4         3921  
31              
32             $VERSION = '2.01';
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 8820 my ($class, $sedol) = @_;
52 52         190 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 361 my $self = shift;
64 266 100       496 $$self = shift if @_;
65 266         741 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 6 my $self = shift;
75 3         7 return substr($self->sedol, 0, 1);
76             }
77              
78             sub _check_format {
79 145     145   187 my $val = shift;
80              
81 145         148 $ERROR = undef;
82              
83 145 100       304 if (length($val) != 7) {
84 12         14 $ERROR = "SEDOLs must be 7 characters long.";
85 12         60 return '';
86             }
87              
88 133 100       386 if ($val =~ /^\d/) {
89             # assume old-style
90 106 100       257 if ($val =~ /\D/) {
91 22         25 $ERROR = "Old-style SEDOLs must contain only numerals.";
92 22         96 return '';
93             }
94             } else {
95             # assume new-style
96 27 100       273 if ($val !~ /^[$valid_alpha]/o) {
    100          
    100          
97 5         9 $ERROR = "New-style SEDOL must have alphabetic first character.";
98 5         27 return '';
99             } elsif ($val !~ /^.[\d$valid_alpha]{5}/o) {
100 3         6 $ERROR = "New-style SEDOL must have alphanumeric characters 2-6.";
101 3         15 return '';
102             } elsif ($val =~ /\D$/) {
103 1         3 $ERROR = "SEDOL checkdigit (character 7) must be numeric.";
104 1         7 return '';
105             }
106             }
107 102         225 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 2271 my $self = shift;
118 72         128 my $val = $self->sedol;
119              
120 72 100       158 return '' unless _check_format($val);
121              
122 39         73 my $c = $self->check_digit;
123 39 100       78 if (substr($self->sedol, -1, 1) eq $c) {
124 12         40 return 1;
125             } else {
126 27         81 $ERROR = "Check digit not correct. Expected $c.";
127 27         83 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 65 shift->is_valid;
139 32         129 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 250 my $self = shift;
152 73         116 my $sedol = $self->sedol;
153 73 100       166 $sedol .= "0" if length($sedol) == 6;
154 73 100       142 return unless _check_format($sedol);
155              
156 63         126 my @val = split //, $self->sedol;
157 63         105 my $sum = 0;
158 63         107 for (0..5) {
159 378         618 $sum += $valid_chars{$val[$_]} * $weights[$_];
160             }
161 63         201 return (10 - $sum % 10) % 10;
162             }
163              
164             1;
165             __END__