{-# LANGUAGE OverloadedStrings #-}
module Data.ISBN.ISBN13
( ISBN(..)
, validateISBN13
, renderISBN13ValidationError
, ISBN13ValidationError(..)
, confirmISBN13CheckDigit
, calculateISBN13CheckDigitValue
, numericValueToISBN13Char
, isISBN13
, unsafeToISBN13
) where
import Control.Monad
import Data.Char
import Data.Text as Text
import Data.ISBN.Types ( ISBN (ISBN13) )
validateISBN13 :: Text -> Either ISBN13ValidationError ISBN
validateISBN13 :: Text -> Either ISBN13ValidationError ISBN
validateISBN13 input :: Text
input = do
let inputWithoutHyphens :: Text
inputWithoutHyphens = (Char -> Bool) -> Text -> Text
Text.filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '-') (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Text -> Text
Text.copy Text
input
Bool
-> Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Int
Text.length Text
inputWithoutHyphens Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 13) (Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ())
-> Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ()
forall a b. (a -> b) -> a -> b
$
ISBN13ValidationError -> Either ISBN13ValidationError ()
forall a b. a -> Either a b
Left ISBN13ValidationError
ISBN13InvalidInputLength
let illegalCharacters :: Text
illegalCharacters = (Char -> Bool) -> Text -> Text
Text.filter (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isNumericCharacter) Text
inputWithoutHyphens
Bool
-> Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Int
Text.length Text
illegalCharacters Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0) (Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ())
-> Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ()
forall a b. (a -> b) -> a -> b
$
ISBN13ValidationError -> Either ISBN13ValidationError ()
forall a b. a -> Either a b
Left ISBN13ValidationError
ISBN13IllegalCharactersInInput
Bool
-> Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text -> Bool
confirmISBN13CheckDigit Text
inputWithoutHyphens) (Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ())
-> Either ISBN13ValidationError ()
-> Either ISBN13ValidationError ()
forall a b. (a -> b) -> a -> b
$
ISBN13ValidationError -> Either ISBN13ValidationError ()
forall a b. a -> Either a b
Left ISBN13ValidationError
ISBN13InvalidCheckDigit
ISBN -> Either ISBN13ValidationError ISBN
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ISBN -> Either ISBN13ValidationError ISBN)
-> ISBN -> Either ISBN13ValidationError ISBN
forall a b. (a -> b) -> a -> b
$ Text -> ISBN
ISBN13 Text
inputWithoutHyphens
data ISBN13ValidationError
= ISBN13InvalidInputLength
| ISBN13IllegalCharactersInInput
| ISBN13InvalidCheckDigit
deriving (Int -> ISBN13ValidationError -> ShowS
[ISBN13ValidationError] -> ShowS
ISBN13ValidationError -> String
(Int -> ISBN13ValidationError -> ShowS)
-> (ISBN13ValidationError -> String)
-> ([ISBN13ValidationError] -> ShowS)
-> Show ISBN13ValidationError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ISBN13ValidationError] -> ShowS
$cshowList :: [ISBN13ValidationError] -> ShowS
show :: ISBN13ValidationError -> String
$cshow :: ISBN13ValidationError -> String
showsPrec :: Int -> ISBN13ValidationError -> ShowS
$cshowsPrec :: Int -> ISBN13ValidationError -> ShowS
Show, ISBN13ValidationError -> ISBN13ValidationError -> Bool
(ISBN13ValidationError -> ISBN13ValidationError -> Bool)
-> (ISBN13ValidationError -> ISBN13ValidationError -> Bool)
-> Eq ISBN13ValidationError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ISBN13ValidationError -> ISBN13ValidationError -> Bool
$c/= :: ISBN13ValidationError -> ISBN13ValidationError -> Bool
== :: ISBN13ValidationError -> ISBN13ValidationError -> Bool
$c== :: ISBN13ValidationError -> ISBN13ValidationError -> Bool
Eq)
renderISBN13ValidationError :: ISBN13ValidationError -> Text
renderISBN13ValidationError :: ISBN13ValidationError -> Text
renderISBN13ValidationError validationError :: ISBN13ValidationError
validationError =
case ISBN13ValidationError
validationError of
ISBN13InvalidInputLength ->
"An ISBN-13 must be 13 characters, not counting hyphens"
ISBN13IllegalCharactersInInput ->
"Every non-hyphen character of an ISBN-13 must be a number"
ISBN13InvalidCheckDigit ->
"The supplied ISBN-13 is not valid"
isNumericCharacter :: Char -> Bool
isNumericCharacter :: Char -> Bool
isNumericCharacter char :: Char
char = Char
char Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ("1234567890" :: String)
confirmISBN13CheckDigit :: Text -> Bool
confirmISBN13CheckDigit :: Text -> Bool
confirmISBN13CheckDigit isbn13 :: Text
isbn13 =
Text -> Int
calculateISBN13CheckDigitValue (Text -> Text
Text.init Text
isbn13) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Char -> Int
isbn13CharToNumericValue (Text -> Char
Text.last Text
isbn13)
calculateISBN13CheckDigitValue :: Text -> Int
calculateISBN13CheckDigitValue :: Text -> Int
calculateISBN13CheckDigitValue input :: Text
input =
Int -> String -> Int -> Int
go 1 (Text -> String
unpack Text
input) 0
where
go :: Int -> String -> Int -> Int
go w :: Int
w charList :: String
charList acc :: Int
acc =
case String
charList of
[] -> (10 Int -> Int -> Int
forall a. Num a => a -> a -> a
- (Int
acc Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 10)) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 10
c :: Char
c:clist :: String
clist -> Int -> String -> Int -> Int
go ((Int
w Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 2) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` 4) String
clist (Int
acc Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
w Int -> Int -> Int
forall a. Num a => a -> a -> a
* Char -> Int
isbn13CharToNumericValue Char
c)
isbn13CharToNumericValue :: Char -> Int
isbn13CharToNumericValue :: Char -> Int
isbn13CharToNumericValue = Char -> Int
digitToInt
numericValueToISBN13Char :: Int -> Char
numericValueToISBN13Char :: Int -> Char
numericValueToISBN13Char c :: Int
c = Text -> Char
Text.head (Text -> Char) -> Text -> Char
forall a b. (a -> b) -> a -> b
$ String -> Text
pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Int -> String
forall a. Show a => a -> String
show Int
c
isISBN13 :: ISBN -> Bool
isISBN13 :: ISBN -> Bool
isISBN13 (ISBN13 _) = Bool
True
isISBN13 _ = Bool
False
unsafeToISBN13 :: Text -> ISBN
unsafeToISBN13 :: Text -> ISBN
unsafeToISBN13 = Text -> ISBN
ISBN13