You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
2.6 KiB
86 lines
2.6 KiB
"""
|
|
This module is responsible for generating the check digit and formatting
|
|
ISBN numbers.
|
|
"""
|
|
from typing import Any, Optional
|
|
|
|
|
|
class ISBN:
|
|
MAX_LENGTH = 13
|
|
|
|
def __init__(
|
|
self,
|
|
ean: Optional[str] = None,
|
|
group: Optional[str] = None,
|
|
registrant: Optional[str] = None,
|
|
publication: Optional[str] = None,
|
|
) -> None:
|
|
self.ean = ean
|
|
self.group = group
|
|
self.registrant = registrant
|
|
self.publication = publication
|
|
|
|
|
|
class ISBN13(ISBN):
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
super().__init__(*args, **kwargs)
|
|
self.check_digit = self._check_digit()
|
|
|
|
def _check_digit(self) -> str:
|
|
"""Calculate the check digit for ISBN-13.
|
|
See https://en.wikipedia.org/wiki/International_Standard_Book_Number
|
|
for calculation.
|
|
"""
|
|
weights = (1 if x % 2 == 0 else 3 for x in range(12))
|
|
body = "".join([part for part in [self.ean, self.group, self.registrant, self.publication] if part is not None])
|
|
remainder = sum(int(b) * w for b, w in zip(body, weights)) % 10
|
|
diff = 10 - remainder
|
|
check_digit = 0 if diff == 10 else diff
|
|
return str(check_digit)
|
|
|
|
def format(self, separator: str = "") -> str:
|
|
return separator.join(
|
|
[
|
|
part
|
|
for part in [
|
|
self.ean,
|
|
self.group,
|
|
self.registrant,
|
|
self.publication,
|
|
self.check_digit,
|
|
]
|
|
if part is not None
|
|
]
|
|
)
|
|
|
|
|
|
class ISBN10(ISBN):
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
super().__init__(*args, **kwargs)
|
|
self.check_digit = self._check_digit()
|
|
|
|
def _check_digit(self) -> str:
|
|
"""Calculate the check digit for ISBN-10.
|
|
See https://en.wikipedia.org/wiki/International_Standard_Book_Number
|
|
for calculation.
|
|
"""
|
|
weights = range(1, 10)
|
|
body = "".join([part for part in [self.group, self.registrant, self.publication] if part is not None])
|
|
remainder = sum(int(b) * w for b, w in zip(body, weights)) % 11
|
|
check_digit = "X" if remainder == 10 else str(remainder)
|
|
return str(check_digit)
|
|
|
|
def format(self, separator: str = "") -> str:
|
|
return separator.join(
|
|
[
|
|
part
|
|
for part in [
|
|
self.group,
|
|
self.registrant,
|
|
self.publication,
|
|
self.check_digit,
|
|
]
|
|
if part is not None
|
|
]
|
|
)
|