From 86c8896ee69b068368b4ef9a4c3923285907c328 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Tue, 18 Mar 2025 15:29:27 +0100 Subject: Parsing ING statements (POC) --- app/Data/Iban.hs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/Data/Iban.hs (limited to 'app/Data/Iban.hs') diff --git a/app/Data/Iban.hs b/app/Data/Iban.hs new file mode 100644 index 0000000..a42e192 --- /dev/null +++ b/app/Data/Iban.hs @@ -0,0 +1,40 @@ +module Data.Iban (Iban, mkIban) where + +import Control.Applicative ((<|>)) +import Data.Attoparsec.Text as AP +import Data.Char + ( digitToInt, + ord, + toUpper, + ) +import Data.Text qualified as T + +newtype Iban = Iban T.Text deriving (Show, Eq) + +mkIban :: T.Text -> Either String Iban +mkIban t = validateIban t >> return (Iban t) + +validateIban :: T.Text -> Either String () +validateIban t = AP.parseOnly ibanP t + where + ibanP = do + countryCode <- AP.count 2 ibanLetter + checkDigits <- AP.count 2 ibanDigit + chars <- AP.many1 ibanChar + endOfInput + if length chars < 30 + then + if valid countryCode checkDigits chars + then return () + else fail $ "IBAN checksum does not match (" ++ T.unpack t ++ ")" + else fail "IBAN has more than 34 characters" + where + ibanChar = ibanDigit <|> ibanLetter + ibanDigit = toInteger . digitToInt <$> AP.digit + ibanLetter = letterToInt <$> AP.letter + letterToInt c = toInteger (ord (toUpper c) - ord 'A' + 10) + charsToInteger = foldl' (\acc d -> if d >= 10 then acc * 100 + d else acc * 10 + d) 0 + ibanToInteger countryCode checkDigits chars = + charsToInteger chars * 1000000 + charsToInteger countryCode * 100 + charsToInteger checkDigits + valid countryCode checkDigits chars = + ibanToInteger countryCode checkDigits chars `mod` 97 == 1 -- cgit v1.2.3