Business-ISIN-0.20/ChangeLog0100644000175000017500000000166407432326551014203 0ustar dpc29dpc29Revision history for Perl module Business::ISIN. 0.20 Wed Feb 13 00:00:22 UTC 2001 - Added pseudo country codes XA, XB, XC, XD and XS. - Made error messages more informative. Thanks to Tim Ayers for the suggestion. 0.12 Tue Oct 9 19:17:35 BST 2001 - Made set() method return the ISIN object, rather than the ISIN as a string, to allow $isin->set("GB0004005475")->is_valid. (Note that stringification means that print $isin->set("GB0004005475") still works). 0.11 Thu Oct 4 21:22:51 BST 2001 - Fixed algorithm bug and checked against thousands of ISINs. Thanks to Peter Dintelmann for providing useful feedback and test data. - Added PS country code (Palestine). 0.10 Sat Sep 29 06:42:39 UTC 2001 - Changed algorithm to cope with ISINs containing letters, such as "US459056DG99". - Checked algorithm against draft copy of update of ISO 6166. 0.01 Sat Mar 10 15:17:41 UTC 2001 - Original version. Business-ISIN-0.20/ISIN.pm0100644000175000017500000001435607432326752013536 0ustar dpc29dpc29####################################################################### # This package validates ISINs and calculates the check digit ####################################################################### package Business::ISIN; use Carp; require 5.005; use strict; use vars qw($VERSION %country_code); $VERSION = '0.20'; use subs qw(check_digit); use overload '""' => \&get; # "$isin" shows value # Get list of valid two-letter country codes. use Locale::Country; $country_code{$_} = 1 for map {uc} Locale::Country::all_country_codes(); # Also include the non-country "country codes", used for bonds issued # in multiple countries, etc.. $country_code{$_} = 1 for qw(XS XA XB XC XD); ####################################################################### # Class Methods ####################################################################### sub new { my $proto = shift; my $initializer = shift; my $class = ref($proto) || $proto; my $self = {value => undef, error => undef}; bless ($self, $class); $self->set($initializer) if defined $initializer; return $self; } ####################################################################### # Object Methods ####################################################################### sub set { my ($self, $isin) = @_; $self->{value} = $isin; return $self; } sub get { my $self = shift; return undef unless $self->is_valid; return $self->{value}; } sub is_valid { # checks if self is a valid ISIN my $self = shift; # return not defined $self->error; # or for speed, do this instead return ( $self->{value} =~ /^(([A-Za-z]{2})([A-Za-z0-9]{9}))([0-9]) $/x and exists $country_code{uc $2} and $4 == check_digit($1) ); } sub error { # returns the error string resulting from failure of is_valid my $self = shift; local $_ = $self->{value}; /^([A-Za-z]{2})? ([A-Za-z0-9]{9})? ([0-9])? (.*)?$/x; return "'$_' does not start with a 2-letter country code" unless length $1 > 0 and exists $country_code{uc $1}; return "'$_' does not have characters 3-11 in [A-Za-z0-9]" unless length $2 > 0; return "'$_' character 12 should be a digit" unless length $3 > 0; return "'$_' has too many characters" unless length $4 == 0; return "'$_' has an inconsistent check digit" unless $3 == check_digit($1.$2); return undef; } ####################################################################### # Subroutines ####################################################################### sub check_digit { # takes a 9 digit string, returns the "double-add-double" check digit my $data = uc shift; $data =~ /^[A-Z]{2}[A-Z0-9]{9}$/ or croak "Invalid data: $data"; $data =~ s/([A-Z])/ord($1) - 55/ge; # A->10, ..., Z->35. my @n = split //, $data; # take individual digits my $max = scalar @n - 1; for my $i (0 .. $max) { if ($i % 2 == 0) { $n[$max - $i] *= 2 } } # double every second digit, starting from the RIGHT hand side. for my $i (@n) { $i = $i % 10 + int $i / 10 } # add digits if >=10 my $sum = 0; for my $i (@n) { $sum += $i } # get the sum of the digits return (10 - $sum) % 10; # tens complement, number between 0 and 9 } 1; __END__ =head1 NAME Business::ISIN - validate International Securities Identification Numbers =head1 VERSION 0.20 =head1 SYNOPSIS use Business::ISIN; my $isin = new Business::ISIN 'US459056DG91'; if ( $isin->is_valid ) { print "$isin is valid!\n"; # or: print $isin->get() . " is valid!\n"; } else { print "Invalid ISIN: " . $isin->error() . "\n"; print "The check digit I was expecting is "; print Business::ISIN::check_digit('US459056DG9') . "\n"; } =head1 REQUIRES Perl5, Locale::Country, Carp =head1 DESCRIPTION C is a class which validates ISINs (International Securities Identification Numbers), the codes which identify shares in much the same way as ISBNs identify books. An ISIN consists of two letters, identifying the country of origin of the security according to ISO 3166, followed by nine characters in [A-Z0-9], followed by a decimal check digit. The C method constructs a new ISIN object. If you give it a scalar argument, it will use the argument to initialize the object's value. Here, no attempt will be made to check that the argument is valid. The C method sets the ISIN's value to a scalar argument which you give. Here, no attempt will be made to check that the argument is valid. The method returns the object, to allow you to do things like C<$isin-Eset("GB0004005475")-Eis_valid>. The C method returns a string, which will be the ISIN's value if it is syntactically valid, and undef otherwise. Interpolating the object reference in double quotes has the same effect (see the synopsis). The C method returns true if the object contains a syntactically valid ISIN. (Note: this does B guarantee that a security actually exists which has that ISIN.) It will return false otherwise. If an object does contain an invalid ISIN, then the C method will return a string explaining what is wrong, like any of the following: =over 4 =item * 'xxx' does not start with a 2-letter country code =item * 'xxx' does not have characters 3-11 in [A-Za-z0-9] =item * 'xxx' character 12 should be a digit =item * 'xxx' has too many characters =item * 'xxx' has an inconsistent check digit =back Otherwise, C will return C. C is an ordinary subroutine and B a class method. It takes a string of the first eleven characters of an ISIN as an argument (e.g. "US459056DG9"), and returns the corresponding check digit, calculated using the so-called 'double-add-double' algorithm. =head1 DIAGNOSTICS C will croak with the message 'Invalid data' if you pass it an unsuitable argument. =head1 ACKNOWLEDGEMENTS Thanks to Peter Dintelmann (Peter.Dintelmann@Dresdner-Bank.com) and Tim Ayers (tim.ayers@reuters.com) for suggestions and help debugging this module. =head1 AUTHOR David Chan =head1 COPYRIGHT Copyright (C) 2002, David Chan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Business-ISIN-0.20/MANIFEST0100644000175000017500000000010507357144064013552 0ustar dpc29dpc29ChangeLog ISIN.pm MANIFEST Makefile.PL README test-isins.txt test.pl Business-ISIN-0.20/Makefile.PL0100644000175000017500000000055607360767074014413 0ustar dpc29dpc29use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( ABSTRACT => 'validate International Securities Identification Numbers', AUTHOR => 'David Chan (david@sheetmusic.org.uk)', NAME => 'Business::ISIN', VERSION_FROM => 'ISIN.pm', # finds $VERSION ); Business-ISIN-0.20/README0100644000175000017500000000021007432327057013275 0ustar dpc29dpc29Business::ISIN - validate International Securities Identification Numbers. By David Chan. Version 0.20 Wed Feb 13 00:00:22 UTC 2001 Business-ISIN-0.20/test-isins.txt0100644000175000017500000000101007357142766015267 0ustar dpc29dpc29AU000000ITE8 AU000MIMWLB8 AU000NCPWXE1 AU00000IWIO9 AU0000BLZAI6 AU000000BAE3 AU00000AISO4 AU0000TENAP4 AU000XPIWPE3 AU000NCPWSP7 AU0000RDFAO9 AU0000XVGWL7 AU000BHPWZB6 AU000BHPWFS2 AU000BHPWZJ9 AU000ANZWMK4 AU000TLSIMC9 AU000XNKWGA1 AU0000XQLKV1 AU000XJOWPR6 DE0007193401 DE0007264301 DE0005272702 DE0005239909 DE0005558696 DE0007037145 DE0006486004 DE0005213508 DE0006619000 DE0005007702 FR0000065666 FR0000121014 CH0002495809 CH0012561640 IT0001441002 FR0004270619 CH0003322606 FR0004039204 FR0000066771 CH0005589418 Business-ISIN-0.20/test.pl0100644000175000017500000000436107432325341013736 0ustar dpc29dpc29# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..12\n"; } END {print "not ok 1\n" unless $loaded;} use Business::ISIN; $loaded = 1; print "ok 1\n"; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $isin = new Business::ISIN; use vars qw($testno); $testno = 2; sub test { # ok if passed a true value my $ok = shift; print $ok ? "ok $testno\n" : "not ok $testno\n"; $testno++; return $ok; } # Check of is_valid $isin->set("GB0004005475"); # right test($isin->is_valid); $isin->set("GB0004005470"); # wrong test(not $isin->is_valid); # Check of get and stringify $isin->set("GB0004005475"); test($isin->get eq "GB0004005475"); $isin->set("GB0004005475"); test("$isin" eq "GB0004005475"); # Check of error messages $isin->set("000invalid00"); test($isin->error eq "'000invalid00' does not start with a 2-letter country code"); $isin->set("aa0000000000"); test($isin->error eq "'aa0000000000' does not start with a 2-letter country code"); $isin->set("gb12%-oops90"); test($isin->error eq "'gb12%-oops90' does not have characters 3-11 in [A-Za-z0-9]"); $isin->set("us123456789X"); test($isin->error eq "'us123456789X' character 12 should be a digit"); $isin->set("gb0004005475hsbc2"); test($isin->error eq "'gb0004005475hsbc2' has too many characters"); $isin->set("gb0000000001"); test($isin->error eq "'gb0000000001' has an inconsistent check digit"); # Check of ISINs containing letters $isin->set("AU0000ZELAM2"); test($isin->is_valid); $isin->set("US459056DG91"); test($isin->is_valid); # Check that set() returns an object test(($isin->set("US459056DG91")->is_valid)); # Check a file full of valid ISINs open my $test, "test-isins.txt" or die "cannot open test-isins.txt: $!"; my @tests = map { chomp; $isin->set($_)->is_valid } <$test>; test(not grep { not $_ } @tests);