1# ismn.py - functions for handling ISMNs 2# 3# Copyright (C) 2010-2017 Arthur de Jong 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18# 02110-1301 USA 19 20"""ISMN (International Standard Music Number). 21 22The ISMN (International Standard Music Number) is used to identify sheet 23music. This module handles both numbers in the 10-digit 13-digit format. 24 25>>> validate('979-0-3452-4680-5') 26'9790345246805' 27>>> validate('9790060115615') 28'9790060115615' 29>>> ismn_type(' M-2306-7118-7') 30'ISMN10' 31>>> validate('9790060115614') 32Traceback (most recent call last): 33 ... 34InvalidChecksum: ... 35>>> compact(' 979-0-3452-4680-5') 36'9790345246805' 37>>> format('9790060115615') 38'979-0-060-11561-5' 39>>> format('M230671187') 40'979-0-2306-7118-7' 41>>> to_ismn13('M230671187') 42'9790230671187' 43""" 44 45from stdnum import ean 46from stdnum.exceptions import * 47from stdnum.util import clean 48 49 50def compact(number): 51 """Convert the ISMN to the minimal representation. This strips the number 52 of any valid ISMN separators and removes surrounding whitespace.""" 53 return clean(number, ' -.').strip().upper() 54 55 56def validate(number): 57 """Check if the number provided is a valid ISMN (either a legacy 10-digit 58 one or a 13-digit one). This checks the length and the check bit but does 59 not check if the publisher is known.""" 60 number = compact(number) 61 if len(number) == 10: 62 if number[0] != 'M': 63 raise InvalidFormat() 64 ean.validate('9790' + number[1:]) 65 elif len(number) == 13: 66 if not number.startswith('9790'): 67 raise InvalidComponent() 68 ean.validate(number) 69 else: 70 raise InvalidLength() 71 return number 72 73 74def ismn_type(number): 75 """Check the type of ISMN number passed and return 'ISMN13', 'ISMN10' 76 or None (for invalid).""" 77 try: 78 number = validate(number) 79 except ValidationError: 80 return None 81 if len(number) == 10: 82 return 'ISMN10' 83 else: # len(number) == 13: 84 return 'ISMN13' 85 86 87def is_valid(number): 88 """Check if the number provided is a valid ISMN (either a legacy 10-digit 89 one or a 13-digit one). This checks the length and the check bit but does 90 not check if the publisher is known.""" 91 try: 92 return bool(validate(number)) 93 except ValidationError: 94 return False 95 96 97def to_ismn13(number): 98 """Convert the number to ISMN13 (EAN) format.""" 99 number = number.strip() 100 min_number = compact(number) 101 if len(min_number) == 13: 102 return number # nothing to do, already 13 digit format 103 # add prefix and strip the M 104 if ' ' in number: 105 return '979 0' + number[1:] 106 elif '-' in number: 107 return '979-0' + number[1:] 108 else: 109 return '9790' + number[1:] 110 111 112# these are the ranges allocated to publisher codes 113_ranges = ( 114 (3, '000', '099'), (4, '1000', '3999'), (5, '40000', '69999'), 115 (6, '700000', '899999'), (7, '9000000', '9999999')) 116 117 118def split(number): 119 """Split the specified ISMN into a bookland prefix (979), an ISMN 120 prefix (0), a publisher element (3 to 7 digits), an item element (2 to 121 6 digits) and a check digit.""" 122 # clean up number 123 number = to_ismn13(compact(number)) 124 # find the correct range and split the number 125 for length, low, high in _ranges: # pragma: no branch (all ranges covered) 126 if low <= number[4:4 + length] <= high: 127 return (number[:3], number[3], number[4:4 + length], 128 number[4 + length:-1], number[-1]) 129 130 131def format(number, separator='-'): 132 """Reformat the number to the standard presentation format with the 133 prefixes, the publisher element, the item element and the check-digit 134 separated by the specified separator. The number is converted to the 135 13-digit format silently.""" 136 return separator.join(x for x in split(number) if x) 137