From 958004665f1f8c56be96fb30ea8f6a60c9c8456b Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Fri, 3 May 2013 23:37:56 +0200 Subject: Implement validate() for ISBN --- stdnum/isbn.py | 58 ++++++++++++++++++++++++++++++++----------------- tests/test_isbn.doctest | 34 +++++++++++++++++++---------- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/stdnum/isbn.py b/stdnum/isbn.py index 0eee01e..9b6c722 100644 --- a/stdnum/isbn.py +++ b/stdnum/isbn.py @@ -1,6 +1,6 @@ # isbn.py - functions for handling ISBNs # -# Copyright (C) 2010, 2011, 2012 Arthur de Jong +# Copyright (C) 2010, 2011, 2012, 2013 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -23,10 +23,12 @@ The ISBN is the International Standard Book Number, used to identify publications. This module supports both numbers in ISBN-10 (10-digit) and ISBN-13 (13-digit) format. ->>> is_valid('978-9024538270') -True ->>> is_valid('978-9024538271') # incorrect check digit -False +>>> validate('978-9024538270') +'9789024538270' +>>> validate('978-9024538271') +Traceback (most recent call last): + ... +InvalidChecksum: ... >>> compact('1-85798-218-5') '1857982185' >>> format('9780471117094') @@ -44,6 +46,7 @@ False """ from stdnum import ean +from stdnum.exceptions import * from stdnum.util import clean @@ -68,24 +71,36 @@ def _calc_isbn10_check_digit(number): return 'X' if check == 10 else str(check) +def validate(number, convert=False): + """Checks to see if the number provided is a valid ISBN (either a legacy + 10-digit one or a 13-digit one). This checks the length and the check + bit but does not check if the group and publisher are valid (use split() + for that).""" + number = compact(number, convert=False) + if not number[:-1].isdigit(): + raise InvalidFormat() + if len(number) == 10: + if _calc_isbn10_check_digit(number[:-1]) != number[-1]: + raise InvalidChecksum() + elif len(number) == 13: + ean.validate(number) + else: + raise InvalidLength() + if convert: + number = to_isbn13(number) + return number + + def isbn_type(number): """Check the passed number and returns 'ISBN13', 'ISBN10' or None (for invalid) for checking the type of number passed.""" try: - number = compact(number) - except: + number = validate(number, convert=False) + except ValidationError: return None if len(number) == 10: - if not number[:-1].isdigit(): - return None - if _calc_isbn10_check_digit(number[:-1]) != number[-1]: - return None return 'ISBN10' elif len(number) == 13: - if not number.isdigit(): - return None - if ean.calc_check_digit(number[:-1]) != number[-1]: - return None return 'ISBN13' @@ -94,13 +109,16 @@ def is_valid(number): 10-digit one or a 13-digit one). This checks the length and the check bit but does not check if the group and publisher are valid (use split() for that).""" - return isbn_type(number) is not None + try: + return bool(validate(number)) + except ValidationError: + return False def to_isbn13(number): """Convert the number to ISBN-13 format.""" number = number.strip() - min_number = compact(number) + min_number = compact(number, convert=False) if len(min_number) == 13: return number # nothing to do, already ISBN-13 # put new check digit in place @@ -117,13 +135,13 @@ def to_isbn13(number): def to_isbn10(number): """Convert the number to ISBN-10 format.""" number = number.strip() - min_number = compact(number) + min_number = compact(number, convert=False) if len(min_number) == 10: return number # nothing to do, already ISBN-13 elif isbn_type(min_number) != 'ISBN13': - raise ValueError('Not a valid ISBN13.') + raise InvalidFormat('Not a valid ISBN13.') elif not number.startswith('978'): - raise ValueError('Does not use 978 Bookland prefix.') + raise InvalidFormat('Does not use 978 Bookland prefix.') # strip EAN prefix number = number[3:-1].strip().strip('-') digit = _calc_isbn10_check_digit(min_number[3:-1]) diff --git a/tests/test_isbn.doctest b/tests/test_isbn.doctest index e759114..d38d51f 100644 --- a/tests/test_isbn.doctest +++ b/tests/test_isbn.doctest @@ -1,6 +1,6 @@ test_isbn.doctest - more detailed doctests for stdnum.isbn module -Copyright (C) 2010, 2011 Arthur de Jong +Copyright (C) 2010, 2011, 2013 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -27,12 +27,22 @@ really useful as module documentation. Tests for mangling and incorect check digits. ->>> isbn.is_valid('08515x-629-2') # added X in the middle -False ->>> isbn.is_valid('85152-629-1') # incorrect check digit -False ->>> isbn.is_valid('978-902453827X') # ISBN with X check digit -False +>>> isbn.validate('08515x-629-2') # added X in the middle +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> isbn.validate('85152-629-1') # incorrect check digit +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> isbn.validate('978-902453827X') # ISBN13 with X check digit +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> isbn.validate('978-902453827') # invalid length +Traceback (most recent call last): + ... +InvalidLength: ... See if ISBN10 to 13 conversion works. @@ -45,10 +55,12 @@ See if ISBN10 to 13 conversion works. '9781857982183' >>> isbn.to_isbn13('1-85798-218-5') '978-1-85798-218-3' ->>> isbn.is_valid(isbn.to_isbn13('1 85798218 5')) -True +>>> isbn.validate(isbn.to_isbn13('1 85798218 5')) +'9781857982183' >>> isbn.compact('1 85798218 5', convert=True) '9781857982183' +>>> isbn.validate('1 85798218 5', convert=True) +'9781857982183' See if ISBN13 to 10 conversion works. @@ -64,11 +76,11 @@ See if ISBN13 to 10 conversion works. >>> isbn.to_isbn10('979-20-1234567-8') # incorrect check digit Traceback (most recent call last): ... -ValueError: Not a valid ISBN13. +InvalidFormat: ... >>> isbn.to_isbn10('9791843123391') Traceback (most recent call last): ... -ValueError: Does not use 978 Bookland prefix. +InvalidFormat: ... Regrouping tests. -- cgit v1.2.3