regluit/core/isbn.py

150 lines
5.1 KiB
Python

## {{{ http://code.activestate.com/recipes/498104/ (r1)
## also http://stackoverflow.com/questions/4047511/checking-if-an-isbn-number-is-correct
import re
def check_digit_10(isbn):
assert len(isbn) == 9
sum = 0
for i in range(len(isbn)):
c = int(isbn[i])
w = i + 1
sum += w * c
r = sum % 11
if r == 10: return 'X'
else: return str(r)
def check_digit_13(isbn):
assert len(isbn) == 12
sum = 0
for i in range(len(isbn)):
c = int(isbn[i])
if i % 2: w = 3
else: w = 1
sum += w * c
r = 10 - (sum % 10)
if r == 10: return '0'
else: return str(r)
def convert_10_to_13(isbn):
assert len(isbn) == 10
prefix = '978' + isbn[:-1]
check = check_digit_13(prefix)
return prefix + check
def strip(s):
"""Strips away any - or spaces. If the remaining string is of length 10 or 13 with digits only in anything but the last
check digit (which may be X), then return '' -- otherwise return the remaining string"""
s = s.replace("-", "").replace(" ", "").upper();
match = re.search(r'^(\d{9}|\d{12})(\d|X)$', s)
if not match:
return None
else:
return s
def convert_13_to_10(isbn):
assert len(isbn) == 13
# only ISBN-13 starting with 978 can have a valid ISBN 10 partner
assert isbn[0:3] == '978'
return isbn [3:12] + check_digit_10(isbn[3:12])
class ISBNException(Exception):
pass
class ISBN(object):
def __init__(self, input_isbn):
self.input_isbn = input_isbn
stripped_isbn = strip(input_isbn)
if stripped_isbn is None:
raise(ISBNException("Input_isbn %s does not seem to be a valid ISBN" % (input_isbn)))
elif len(stripped_isbn) == 10:
self.__type = '10'
self.__isbn10 = stripped_isbn
self.__valid_10 = stripped_isbn[0:9] + check_digit_10(stripped_isbn[0:9])
self.__valid = (self.__isbn10 == self.__valid_10)
# this is the corresponding ISBN 13 based on the assumption of a valid ISBN 10
self.__isbn13 = convert_10_to_13(stripped_isbn)
self.__valid_13 = self.__isbn13
elif len(stripped_isbn) == 13:
# Assume ISBN 13 all have to begin with 978 or 979 and only 978 ISBNs can possibly have ISBN-10 counterpart
if stripped_isbn[0:3] not in ['978','979']:
raise (ISBNException("ISBN 13 must begin with 978 or 979 not %s " % (stripped_isbn[0:3])))
self.__type = '13'
self.__isbn13 = stripped_isbn
self.__valid_13 = stripped_isbn[0:12] + check_digit_13(stripped_isbn[0:12])
self.__valid = (self.__isbn13 == self.__valid_13)
# now check to see whether the isbn starts with 978 -- only then convert to ISBN -10
if self.__isbn13[0:3] == '978':
self.__isbn10 = convert_13_to_10(stripped_isbn)
self.__valid_10 = self.__isbn10
else:
self.__isbn10 = None
self.__valid_10 = None
else:
raise(ISBNException("Parsed ISBN %s is of the wrong length" % (stripped_isbn)))
@property
def type(self):
return self.__type
@property
def valid (self):
return self.__valid
def validate (self):
""" replace the ISBN value with the checksumed version """
if self.type == '10':
self.__isbn10 = self.__valid_10
self.__valid = True
return self
else:
self.__isbn13 = self.__valid_13
self.__valid = True
return self
def to_string(self, type='13', hyphenate=False):
if type == '10' or type == 10:
if self.__isbn10 is None:
raise (ISBNException("No ISBN-10 exists for %s" % (self.__isbn13)))
if hyphenate:
s = self.__isbn10
return "%s-%s-%s-%s" % (s[0], s[1:4], s[4:9], s[9])
else:
return self.__isbn10
else:
if hyphenate:
s = self.__isbn13
return "%s-%s-%s-%s-%s" % (s[0:3], s[3], s[4:7], s[7:12], s[12])
else:
return self.__isbn13
def __unicode__(self):
return unicode(self.to_string(type=self.type,hyphenate=False))
def __str__(self):
return self.to_string(type=self.type,hyphenate=False)
def __eq__(self, other):
""" both equal if both valid checksums and ISBN 13 equal """
if isinstance(other, ISBN):
if (self.valid and other.valid) and (self.to_string('13') == other.to_string('13')):
return True
else:
return False
else:
try:
other_isbn = ISBN(other)
if (self.valid and other_isbn.valid) and (self.to_string('13') == other_isbn.to_string('13')):
return True
else:
return False
except:
return False
def __ne__(self, other):
return not (self.__eq__(other))