regluit/core/isbn.py

199 lines
6.0 KiB
Python

# encoding: utf-8
## {{{ http://code.activestate.com/recipes/498104/ (r1)
## also https://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 convert_10_to_13(isbn):
try:
isbn = ISBN(isbn)
isbn.validate()
if isbn.valid:
return isbn.to_string()
else:
return None
except:
return None
ISBN_REGEX = re.compile(r'^(\d{9}[\dX]|\d{13})$')
DASH_REGEX = re.compile(u'[ \\-–—‐,;]+') #includes unicode hyphen, endash and emdash
def strip(s):
"""Strips away any - or spaces and some punctuation. 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
"""
try:
s = DASH_REGEX.sub('', s).upper()
match = ISBN_REGEX.search(s)
except:
return None
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])
def convert_13_to_10(isbn):
try:
isbn = ISBN(isbn)
isbn.validate()
if isbn.valid:
return isbn.to_string(type='10')
else:
return None
except:
return None
class ISBNException(Exception):
pass
class ISBN(object):
def __init__(self, input_isbn):
self.input_isbn = input_isbn
self.error = None
stripped_isbn = strip(input_isbn)
if stripped_isbn is None or len(stripped_isbn) not in (10, 13):
self.error = "input_isbn does not seem to be a valid ISBN"
self.__type = None
self.__valid = False
self.__isbn10 = None
self.__valid_10 = None
self.__isbn13 = None
self.__valid_13 = None
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
self.__type = '13'
self.__isbn13 = stripped_isbn
if stripped_isbn[0:3] not in ['978','979']:
self.error = "ISBN 13 must begin with 978 or 979 not %s " % (stripped_isbn[0:3])
self.__valid = False
self.__valid_13 = None
else:
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
@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 not self.__valid:
return None
if type == '10' or type == 10:
if self.__isbn10 is None:
return None
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):
s = self.to_string(type=self.type, hyphenate=False)
if s is not None:
return s
else:
return ''
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))