1####################################################################### 2# This package validates ISINs and calculates the check digit 3####################################################################### 4 5package Business::ISIN; 6use Carp; 7require 5.005; 8 9use strict; 10use vars qw($VERSION %country_code); 11$VERSION = '0.20'; 12 13use subs qw(check_digit); 14use overload '""' => \&get; # "$isin" shows value 15 16 17# Get list of valid two-letter country codes. 18use Locale::Country; 19$country_code{$_} = 1 for map {uc} Locale::Country::all_country_codes(); 20 21# Also include the non-country "country codes", used for bonds issued 22# in multiple countries, etc.. 23$country_code{$_} = 1 for qw(XS XA XB XC XD); 24####################################################################### 25# Class Methods 26####################################################################### 27 28sub new { 29 my $proto = shift; 30 my $initializer = shift; 31 32 my $class = ref($proto) || $proto; 33 my $self = {value => undef, error => undef}; 34 bless ($self, $class); 35 36 $self->set($initializer) if defined $initializer; 37 return $self; 38} 39 40####################################################################### 41# Object Methods 42####################################################################### 43 44sub set { 45 my ($self, $isin) = @_; 46 $self->{value} = $isin; 47 return $self; 48} 49 50sub get { 51 my $self = shift; 52 return undef unless $self->is_valid; 53 return $self->{value}; 54} 55 56sub is_valid { # checks if self is a valid ISIN 57 my $self = shift; 58 59 # return not defined $self->error; # or for speed, do this instead 60 return ( 61 $self->{value} =~ /^(([A-Za-z]{2})([A-Za-z0-9]{9}))([0-9]) $/x 62 and exists $country_code{uc $2} 63 and $4 == check_digit($1) 64 ); 65} 66 67sub error { 68 # returns the error string resulting from failure of is_valid 69 my $self = shift; 70 local $_ = $self->{value}; 71 72 /^([A-Za-z]{2})? ([A-Za-z0-9]{9})? ([0-9])? (.*)?$/x; 73 74 return "'$_' does not start with a 2-letter country code" 75 unless length $1 > 0 and exists $country_code{uc $1}; 76 77 return "'$_' does not have characters 3-11 in [A-Za-z0-9]" 78 unless length $2 > 0; 79 80 return "'$_' character 12 should be a digit" 81 unless length $3 > 0; 82 83 return "'$_' has too many characters" 84 unless length $4 == 0; 85 86 return "'$_' has an inconsistent check digit" 87 unless $3 == check_digit($1.$2); 88 89 return undef; 90} 91 92 93####################################################################### 94# Subroutines 95####################################################################### 96 97sub check_digit { 98 # takes a 9 digit string, returns the "double-add-double" check digit 99 my $data = uc shift; 100 101 $data =~ /^[A-Z]{2}[A-Z0-9]{9}$/ or croak "Invalid data: $data"; 102 103 $data =~ s/([A-Z])/ord($1) - 55/ge; # A->10, ..., Z->35. 104 105 my @n = split //, $data; # take individual digits 106 107 my $max = scalar @n - 1; 108 for my $i (0 .. $max) { if ($i % 2 == 0) { $n[$max - $i] *= 2 } } 109 # double every second digit, starting from the RIGHT hand side. 110 111 for my $i (@n) { $i = $i % 10 + int $i / 10 } # add digits if >=10 112 113 my $sum = 0; for my $i (@n) { $sum += $i } # get the sum of the digits 114 115 return (10 - $sum) % 10; # tens complement, number between 0 and 9 116} 117 1181; 119 120 121 122__END__ 123 124=head1 NAME 125 126Business::ISIN - validate International Securities Identification Numbers 127 128=head1 VERSION 129 1300.20 131 132=head1 SYNOPSIS 133 134 use Business::ISIN; 135 136 my $isin = new Business::ISIN 'US459056DG91'; 137 138 if ( $isin->is_valid ) { 139 print "$isin is valid!\n"; 140 # or: print $isin->get() . " is valid!\n"; 141 } else { 142 print "Invalid ISIN: " . $isin->error() . "\n"; 143 print "The check digit I was expecting is "; 144 print Business::ISIN::check_digit('US459056DG9') . "\n"; 145 } 146 147=head1 REQUIRES 148 149Perl5, Locale::Country, Carp 150 151=head1 DESCRIPTION 152 153C<Business::ISIN> is a class which validates ISINs (International Securities 154Identification Numbers), the codes which identify shares in much the same 155way as ISBNs identify books. An ISIN consists of two letters, identifying 156the country of origin of the security according to ISO 3166, followed by 157nine characters in [A-Z0-9], followed by a decimal check digit. 158 159The C<new()> method constructs a new ISIN object. If you give it a scalar 160argument, it will use the argument to initialize the object's value. Here, 161no attempt will be made to check that the argument is valid. 162 163The C<set()> method sets the ISIN's value to a scalar argument which you 164give. Here, no attempt will be made to check that the argument is valid. 165The method returns the object, to allow you to do things like 166C<$isin-E<gt>set("GB0004005475")-E<gt>is_valid>. 167 168The C<get()> method returns a string, which will be the ISIN's value if it 169is syntactically valid, and undef otherwise. Interpolating the object 170reference in double quotes has the same effect (see the synopsis). 171 172The C<is_valid()> method returns true if the object contains a syntactically 173valid ISIN. (Note: this does B<not> guarantee that a security actually 174exists which has that ISIN.) It will return false otherwise. 175 176If an object does contain an invalid ISIN, then the C<error()> method will 177return a string explaining what is wrong, like any of the following: 178 179=over 4 180 181=item * 'xxx' does not start with a 2-letter country code 182 183=item * 'xxx' does not have characters 3-11 in [A-Za-z0-9] 184 185=item * 'xxx' character 12 should be a digit 186 187=item * 'xxx' has too many characters 188 189=item * 'xxx' has an inconsistent check digit 190 191=back 192 193Otherwise, C<error()> will return C<undef>. 194 195C<check_digit()> is an ordinary subroutine and B<not> a class method. It 196takes a string of the first eleven characters of an ISIN as an argument (e.g. 197"US459056DG9"), and returns the corresponding check digit, calculated using 198the so-called 'double-add-double' algorithm. 199 200=head1 DIAGNOSTICS 201 202C<check_digit()> will croak with the message 'Invalid data' if you pass it 203an unsuitable argument. 204 205=head1 ACKNOWLEDGEMENTS 206 207Thanks to Peter Dintelmann (Peter.Dintelmann@Dresdner-Bank.com) and Tim 208Ayers (tim.ayers@reuters.com) for suggestions and help debugging this 209module. 210 211=head1 AUTHOR 212 213David Chan <david@sheetmusic.org.uk> 214 215=head1 COPYRIGHT 216 217Copyright (C) 2002, David Chan. All rights reserved. This program is free 218software; you can redistribute it and/or modify it under the same terms as 219Perl itself. 220