In today’s Programming Praxis exercise, our goal is to write a number of functions related to ISBN numbers. Let’s get started, shall we?
Some imports:
import Control.Applicative hiding ((<|>), optional) import Data.Char import Data.List import Data.Map (elems) import Network.HTTP import Text.HJson import Text.HJson.Query import Text.Parsec
First, we need some parsers for ISBN and EAN numbers.
isbn = (++) <$> (concat <$> sepEndBy1 (many1 d) (oneOf " -")) <*> option [] ([10] <$ char 'X') where d = read . return <$> digit ean = string "978" *> optional (oneOf " -") *> isbn
Since we need the check digits both for validation and conversion we make separate functions for them.
isbnCheck, eanCheck :: Integral a => [a] -> a isbnCheck n = 11 - mod (sum $ zipWith (*) [10,9..] (take 9 n)) 11 eanCheck n = mod (sum $ zipWith (*) (cycle [1,3]) (take 9 n)) 10
Checking whether a number is valid is a matter of checking if the length and last digit are correct.
validISBN, validEAN :: String -> Bool validISBN = valid isbn isbnCheck validEAN = valid ean eanCheck valid p c = either (const False) v . parse p "" where v ds = length ds == 10 && c ds == last ds
Conversion just requires changing the last digit.
toISBN, toEAN :: String -> Maybe String toISBN = convert ean isbnCheck toEAN = fmap ("978-" ++) . convert isbn eanCheck convert p c = either (const Nothing) (Just . fixCheck) . parse p "" where fixCheck n = map intToDigit (init n) ++ [check $ c n] check n = if n == 10 then 'X' else intToDigit n
Since I don’t like APIs that require an access key, we’ll be using openlibrary instead of isbndb.
lookupISBN :: String -> IO [(String, [String])] lookupISBN = get . ("http://openlibrary.org/api/books?format=json&\ \jscmd=data&bibkeys=ISBN:" ++) where f ~(JObject j) = map (\b -> (unjs $ key "title" b, map (unjs . key "name") . getFromArr $ key "authors" b)) $ elems j key k = head . getFromKey k unjs ~(JString s) = s get url = fmap (either (const undefined) f . fromString) . getResponseBody =<< simpleHTTP (getRequest url)
Some tests to see if everything is working properly:
main :: IO () main = do print $ validISBN "99921-58-10-7" print $ validISBN "80-902734-1-6" print $ validISBN "0-943396-04-2" print $ validISBN "0-9752298-0-X" print $ validISBN "0943396042" print $ not $ validISBN "99921-58-10-8" print $ not $ validISBN "99921-58-10-" print $ not $ validISBN "9" print $ validEAN "978-0-0700048-4-9" print $ validEAN "9780070004849" print $ not $ validEAN "9780070004848" print $ toISBN "9780070004849" == Just "0070004846" print $ toEAN "0070004846" == Just "978-0070004849" mapM_ (\(t,a) -> putStrLn ("Title: " ++ t) >> putStrLn ("Authors: " ++ intercalate ", " a)) =<< lookupISBN "0070004846"
Tags: bonsai, code, ean, Haskell, isbn, kata, number, praxis, programming
Leave a Reply