1# issn.py - functions for handling ISSNs
2#
3# Copyright (C) 2010-2015 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"""ISSN (International Standard Serial Number).
21
22The ISSN (International Standard Serial Number) is the standard code to
23identify periodical publications (e.g. magazines).
24
25An ISSN has 8 digits and is formatted in two pairs of 4 digits separated by a
26hyphen. The last digit is a check digit and may be 0-9 or X (similar to
27ISBN-10).
28
29More information:
30
31* https://en.wikipedia.org/wiki/International_Standard_Serial_Number
32* http://www.issn.org/
33
34>>> validate('0024-9319')
35'00249319'
36>>> validate('0032147X')
37Traceback (most recent call last):
38    ...
39InvalidChecksum: ...
40>>> validate('003214712')
41Traceback (most recent call last):
42    ...
43InvalidLength: ...
44>>> compact('0032-1478')
45'00321478'
46>>> format('00249319')
47'0024-9319'
48>>> to_ean('0264-3596')
49'9770264359008'
50"""
51
52from stdnum import ean
53from stdnum.exceptions import *
54from stdnum.util import clean, isdigits
55
56
57def compact(number):
58    """Convert the ISSN to the minimal representation. This strips the number
59    of any valid ISSN separators and removes surrounding whitespace."""
60    return clean(number, ' -').strip().upper()
61
62
63def calc_check_digit(number):
64    """Calculate the ISSN check digit for 10-digit numbers. The number passed
65    should not have the check bit included."""
66    check = (11 - sum((8 - i) * int(n)
67                      for i, n in enumerate(number))) % 11
68    return 'X' if check == 10 else str(check)
69
70
71def validate(number):
72    """Check if the number is a valid ISSN. This checks the length and
73    whether the check digit is correct."""
74    number = compact(number)
75    if not isdigits(number[:-1]):
76        raise InvalidFormat()
77    if len(number) != 8:
78        raise InvalidLength()
79    if calc_check_digit(number[:-1]) != number[-1]:
80        raise InvalidChecksum()
81    return number
82
83
84def is_valid(number):
85    """Check if the number provided is a valid ISSN."""
86    try:
87        return bool(validate(number))
88    except ValidationError:
89        return False
90
91
92def format(number):
93    """Reformat the number to the standard presentation format."""
94    number = compact(number)
95    return number[:4] + '-' + number[4:]
96
97
98def to_ean(number, issue_code='00'):
99    """Convert the number to EAN-13 format."""
100    number = '977' + validate(number)[:-1] + issue_code
101    return number + ean.calc_check_digit(number)
102