10桁・13桁のISBNを相互に変換するPerlワンライナー

ISBN(世界的に使われている書籍の識別番号)には、10桁のものと13桁のものがあります。

http://ja.wikipedia.org/wiki/ISBN

仕事でISBNを相互に変換するプログラムが必要になったのですが、CPANモジュール(Business::ISBN)を使うほどの処理でもないし、単純な実装のものが見つからなかったので自前で実装しました。ついでに、これワンライナーでも書けるんじゃね、と思い立ったのでPerlワンライナーも作ってみました(ちょっと冗長なのでワンライナーにする意味はあまり無かったかなぁとも思いつつ)。

ISBN-13 → ISBN-10

perl -le '@a=map{$s+=(10-$i++)*$_;$_;}(grep{/\d/}split(//,shift))[3..11];$d=(11-$s%11)%11;print @a,$d==10?q{X}:$d' [ISBN]

ISBN-13 → ISBN-10(ハイフン付き)

perl -le '@a=map{$s+=(10-$i++)*$_;$_;}(grep{/\d/}split(//,shift))[3..11];$d=(11-$s%11)%11;printf(qq{%d-%d%d%d%d-%d%d%d%d-%d\n},@a,$d==10?q{X}:$d' [ISBN]

ISBN-10 → ISBN-13

perl -le '@a=map{$s+=($i++%2?1:3)*$_;$_;}(grep{/\d/}split(//,shift))[0..8];print 978,@a,(10-($s+8)%10)%10' [ISBN-10]

ISBN-10 → ISBN-13(ハイフン付き)

perl -le '@a=map{$s+=($i++%2?1:3)*$_;$_;}(grep{/\d/}split(//,shift))[0..8];printf(qq{978-%d-%d%d%d%d-%d%d%d%d-%d\n},@a,(10-($s+8)%10)%10)' [ISBN-10]

[ISBN-13] あるいは [ISBN-10] を実際の ISBNに置き換えて実行してください。指定するISBNは、ハイフン有りでも無しでも構いません。

ワンライナーですので、エラー処理などはしていません。不正なISBNを渡すと不正な値を返すと思います。

ちゃんと書くと、以下のようになるのでしょうかね。

# ISBN13 → ISBN10
sub isbn13_to_10 {
	my ( $isbn, $hyphen ) = @_;
	$isbn =~ s/\D//g;
	return if ( ! $isbn || $isbn !~ /^\d{13}$/ );
	
	my @isbn10 = (split(//, $isbn))[3..11];
	push @isbn10, isbn10_digit( @isbn10 );
	
	return sprintf( "%d-%d%d%d%d-%d%d%d%d-%d", @isbn10 ) if ( $hyphen );
	return join( '', @isbn10  );
}

# ISBN-10 のチェックディジットを求める
sub isbn10_digit {
	my @ints = @_;
	my $i = 0;
	my $sum = 0;
	foreach my $int ( @ints ) {
		$sum += (10 - $i) * $int;
		$i++;
	}
	my $digit = ( 11 - $sum % 11 ) % 11;
	return ($digit == 10 ? 'X' : $digit);
}

# ISBN10 → ISBN13
sub isbn10_to_13 {
	my ( $isbn, $hyphen, $prefix ) = @_;
	$prefix ||= '978';

	$isbn =~ s/[^0-9x]//ig;
	return if ( ! $isbn || $isbn !~ /^\d{9}[0-9x]$/i );
	
	my @isbn13 = ( split(//, $prefix), (split(//, $isbn))[0..8] );
	push @isbn13, isbn13_digit( @isbn13 );
	
	return sprintf( "%d%d%d-%d-%d%d%d%d-%d%d%d%d-%d", @isbn13 ) if ( $hyphen );
	return join( '', @isbn13 );
}

# ISBN-13 のチェックディジットを求める
sub isbn13_digit {
	my @ints = @_;
	my $i = 0;
	my $sum = 0;
	foreach my $int ( @ints ) {
		$sum += ($i % 2 ? 3 : 1) * $int;
		$i++
	}
	return ( 10 - $sum % 10 ) % 10;
}


# ISBNとして正しくない場合、正しいISBNを返す
# 正しいISBNを求められない場合は、-1 を返す
sub isnt_isbn {
	my $isbn = shift;
	$isbn =~ s/[^0-9x]//ig;
	return -1 if ( ! $isbn );
	
	if ( $isbn =~ /^\d{9}[0-9x]$/i ) {
		my @arr = (split(//, $isbn))[0..8];
		my $isbn10 = join( '', @arr, isbn10_digit( @arr ) );
		return $isbn10 if ( uc( $isbn ) ne $isbn10 );
		return;

	} elsif ( $isbn =~ /^\d{13}$/ ) {
		my @arr = (split(//, $isbn))[0..11];
		my $isbn13 = join( '', @arr, isbn13_digit( @arr ) );
		return $isbn13 if ( $isbn ne $isbn13 );
		return;
	}
	return -1;
}