{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.ISBN
(
ISBN
, renderISBN
, validateISBN
, ISBNValidationError(..)
, renderISBNValidationError
, validateISBN10
, ISBN10ValidationError
, renderISBN10ValidationError
, validateISBN13
, ISBN13ValidationError(..)
, renderISBN13ValidationError
, convertISBN10toISBN13
, convertISBN13toISBN10
, isISBN10
, isISBN13
, unsafeToISBN10
, unsafeToISBN13
) where
import Data.ISBN.ISBN10
import Data.ISBN.ISBN13
import Control.Monad
import Data.Text ( Text )
import qualified Data.Text as Text
validateISBN :: Text -> Either ISBNValidationError ISBN
validateISBN :: Text -> Either ISBNValidationError ISBN
validateISBN Text
isbn = do
let isbn10result :: Either ISBN10ValidationError ISBN
isbn10result = Text -> Either ISBN10ValidationError ISBN
validateISBN10 Text
isbn
isbn13result :: Either ISBN13ValidationError ISBN
isbn13result = Text -> Either ISBN13ValidationError ISBN
validateISBN13 Text
isbn
case (Either ISBN10ValidationError ISBN
isbn10result, Either ISBN13ValidationError ISBN
isbn13result) of
(Right ISBN
isbn10, Either ISBN13ValidationError ISBN
_) ->
ISBN -> Either ISBNValidationError ISBN
forall a b. b -> Either a b
Right ISBN
isbn10
(Either ISBN10ValidationError ISBN
_, Right ISBN
isbn13) ->
ISBN -> Either ISBNValidationError ISBN
forall a b. b -> Either a b
Right ISBN
isbn13
(Left ISBN10ValidationError
ISBN10InvalidInputLength, Left ISBN13ValidationError
ISBN13InvalidInputLength) ->
ISBNValidationError -> Either ISBNValidationError ISBN
forall a b. a -> Either a b
Left ISBNValidationError
InvalidISBNInputLength
(Left ISBN10ValidationError
ISBN10IllegalCharactersInBody, Either ISBN13ValidationError ISBN
_) ->
ISBNValidationError -> Either ISBNValidationError ISBN
forall a b. a -> Either a b
Left ISBNValidationError
IllegalCharactersInISBN10Body
(Left ISBN10ValidationError
ISBN10IllegalCharacterAsCheckDigit, Either ISBN13ValidationError ISBN
_) ->
ISBNValidationError -> Either ISBNValidationError ISBN
forall a b. a -> Either a b
Left ISBNValidationError
IllegalCharacterAsISBN10CheckDigit
(Either ISBN10ValidationError ISBN
_ , Left ISBN13ValidationError
ISBN13IllegalCharactersInInput) ->
ISBNValidationError -> Either ISBNValidationError ISBN
forall a b. a -> Either a b
Left ISBNValidationError
IllegalCharactersInISBN13Input
(Left ISBN10ValidationError
ISBN10InvalidCheckDigit, Either ISBN13ValidationError ISBN
_) ->
ISBNValidationError -> Either ISBNValidationError ISBN
forall a b. a -> Either a b
Left ISBNValidationError
InvalidISBN10CheckDigit
(Either ISBN10ValidationError ISBN
_, Left ISBN13ValidationError
ISBN13InvalidCheckDigit) ->
ISBNValidationError -> Either ISBNValidationError ISBN
forall a b. a -> Either a b
Left ISBNValidationError
InvalidISBN13CheckDigit
renderISBN :: ISBN -> Text
renderISBN :: ISBN -> Text
renderISBN (ISBN10 Text
i) = Text
i
renderISBN (ISBN13 Text
i) = Text
i
data ISBNValidationError
= InvalidISBNInputLength
| IllegalCharactersInISBN10Body
| IllegalCharactersInISBN13Input
| IllegalCharacterAsISBN10CheckDigit
| InvalidISBN10CheckDigit
| InvalidISBN13CheckDigit
deriving (Int -> ISBNValidationError -> ShowS
[ISBNValidationError] -> ShowS
ISBNValidationError -> String
(Int -> ISBNValidationError -> ShowS)
-> (ISBNValidationError -> String)
-> ([ISBNValidationError] -> ShowS)
-> Show ISBNValidationError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ISBNValidationError] -> ShowS
$cshowList :: [ISBNValidationError] -> ShowS
show :: ISBNValidationError -> String
$cshow :: ISBNValidationError -> String
showsPrec :: Int -> ISBNValidationError -> ShowS
$cshowsPrec :: Int -> ISBNValidationError -> ShowS
Show, ISBNValidationError -> ISBNValidationError -> Bool
(ISBNValidationError -> ISBNValidationError -> Bool)
-> (ISBNValidationError -> ISBNValidationError -> Bool)
-> Eq ISBNValidationError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ISBNValidationError -> ISBNValidationError -> Bool
$c/= :: ISBNValidationError -> ISBNValidationError -> Bool
== :: ISBNValidationError -> ISBNValidationError -> Bool
$c== :: ISBNValidationError -> ISBNValidationError -> Bool
Eq)
renderISBNValidationError :: ISBNValidationError -> Text
renderISBNValidationError :: ISBNValidationError -> Text
renderISBNValidationError ISBNValidationError
validationError =
case ISBNValidationError
validationError of
ISBNValidationError
InvalidISBNInputLength ->
Text
"An ISBN must be 10 or 13 characters, not counting hyphens"
ISBNValidationError
IllegalCharactersInISBN10Body ->
Text
"The first nine non-hyphen characters of an ISBN-10 must all be numbers"
ISBNValidationError
IllegalCharactersInISBN13Input ->
Text
"Every non-hyphen character of an ISBN-13 must be a number"
ISBNValidationError
IllegalCharacterAsISBN10CheckDigit ->
Text
"The last character of an ISBN-10 must be a number or the letter 'X'"
ISBNValidationError
InvalidISBN10CheckDigit ->
Text
"The supplied ISBN-10 is not valid"
ISBNValidationError
InvalidISBN13CheckDigit ->
Text
"The supplied ISBN-13 is not valid"
convertISBN10toISBN13 :: ISBN -> ISBN
convertISBN10toISBN13 :: ISBN -> ISBN
convertISBN10toISBN13 ISBN
isbn10 =
Text -> ISBN
unsafeToISBN13 (Text -> ISBN) -> Text -> ISBN
forall a b. (a -> b) -> a -> b
$ Text
isbn13Body Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
isbn13CheckDigit
where
isbn13CheckDigit :: Text
isbn13CheckDigit = Char -> Text
Text.singleton (Char -> Text) -> (Int -> Char) -> Int -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
numericValueToISBN13Char (Int -> Text) -> Int -> Text
forall a b. (a -> b) -> a -> b
$ Text -> Int
calculateISBN13CheckDigitValue Text
isbn13Body
isbn13Body :: Text
isbn13Body = Text
"978" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
isbn10Body
isbn10Body :: Text
isbn10Body = Text -> Text
Text.init (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ ISBN -> Text
renderISBN ISBN
isbn10
convertISBN13toISBN10 :: ISBN -> Maybe ISBN
convertISBN13toISBN10 :: ISBN -> Maybe ISBN
convertISBN13toISBN10 ISBN
isbn13 = do
let isbn13Text :: Text
isbn13Text = ISBN -> Text
renderISBN ISBN
isbn13
Bool -> Maybe () -> Maybe ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Text
"978" Text -> Text -> Bool
`Text.isPrefixOf` Text
isbn13Text)
Maybe ()
forall a. Maybe a
Nothing
let isbn10Body :: Text
isbn10Body = Text -> Text
Text.init (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
Text.drop Int
3 Text
isbn13Text
isbn10CheckDigit :: Text
isbn10CheckDigit = Char -> Text
Text.singleton (Char -> Text) -> (Int -> Char) -> Int -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
numericValueToISBN10Char (Int -> Text) -> Int -> Text
forall a b. (a -> b) -> a -> b
$ Text -> Int
calculateISBN10CheckDigitValue Text
isbn10Body
ISBN -> Maybe ISBN
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ISBN -> Maybe ISBN) -> ISBN -> Maybe ISBN
forall a b. (a -> b) -> a -> b
$ Text -> ISBN
unsafeToISBN10 (Text -> ISBN) -> Text -> ISBN
forall a b. (a -> b) -> a -> b
$ Text
isbn10Body Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
isbn10CheckDigit