LslPlus-0.4.0: src/Language/Lsl/Internal/DOMCombinators.hs
{-# OPTIONS_GHC -XNoMonomorphismRestriction #-}
module Language.Lsl.Internal.DOMCombinators where
import Control.Monad.State
import Control.Monad.Error
import Data.Maybe
import Language.Lsl.Internal.DOMProcessing
import Text.XML.HaXml(Attribute,AttValue(..),Document(..),Element(..),Content(..),Reference(..),xmlParse,info)
import Text.XML.HaXml.Posn(Posn(..),noPos)
import Language.Lsl.Internal.Util(readM)
import Debug.Trace
type ContentAcceptor a = [Content Posn] -> Either String a
type ContentFinder a = StateT [Content Posn] (Either String) a
type ElementAcceptor a = Posn -> Element Posn -> Either String a
type ElementTester a = Posn -> Element Posn -> Either String (Maybe a)
type AttributeAcceptor a = Posn -> [Attribute] -> Either String a
type AttributeFinder a = StateT (Posn,[Attribute]) (Either String) a
type AttributeTester a = Posn -> Attribute -> Either String (Maybe a)
type AttributesTester a = Posn -> [Attribute] -> Either String (Maybe a)
el :: String -> (b -> a) -> ContentAcceptor b -> ElementTester a
el tag f cf p (Elem name _ cs) | tag /= name = Right Nothing
| otherwise = case cf cs of
Left s -> Left ("at " ++ show p ++ ": " ++ s)
Right v -> Right (Just (f v))
elWith :: String -> (a -> b -> c) -> AttributeAcceptor (Maybe a) -> ContentAcceptor b -> ElementTester c
elWith tag f af cf p (Elem name attrs cs) | tag /= name = Right Nothing
| otherwise = do
av <- af p attrs
case av of
Nothing -> Right Nothing
Just av -> do
cv <- cf cs
return (Just (f av cv))
liftElemTester :: (Posn -> (Element Posn) -> Either String (Maybe a)) -> (Content Posn -> Either String (Maybe a))
liftElemTester ef (CElem e pos) = case ef pos e of
Left s -> Left ("at " ++ show pos ++ ": " ++ s)
Right v -> Right v
canHaveElem :: ElementTester a -> ContentFinder (Maybe a)
canHaveElem ef = get >>= \ cs ->
mapM (\ c -> (lift . liftElemTester ef) c >>= return . (,) c) [ e | e@(CElem _ _) <- cs ]
>>= (\ vs -> case span (isNothing . snd) vs of
(bs,[]) -> put (map fst bs) >> return Nothing
(bs,c:cs) -> put (map fst (bs ++ cs)) >> return (snd c))
mustHaveElem :: ElementTester a -> ContentFinder a
mustHaveElem ef = get >>= \ cs ->
mapM (\ c -> (lift . liftElemTester ef) c >>= return . (,) c) [ e | e@(CElem _ _) <- cs ]
>>= (\ vs -> case span (isNothing . snd) vs of
(bs,[]) -> throwError ("element not found")
(bs,c:cs) -> put (map fst (bs ++ cs)) >> return (fromJust $ snd c))
mustHave :: String -> ContentAcceptor a -> ContentFinder a
mustHave s ca = catchError (mustHaveElem (el s id ca)) (\ e -> throwError (e ++ " (" ++ s ++ ")"))
canHave :: String -> ContentAcceptor a -> ContentFinder (Maybe a)
canHave s ca = canHaveElem (el s id ca)
comprises :: ContentFinder a -> ContentAcceptor a
comprises cf cs = case runStateT cf cs of
Left s -> throwError s
Right (v,cs') -> empty cs' >> return v
many :: ElementTester a -> ContentAcceptor [a]
many et cs = case runStateT go cs of
Left s -> throwError ("many: " ++ s)
Right (v,cs') -> empty cs' >> return v
where go = do
isEmpty <- get >>= return . null
if isEmpty then return []
else do
v <- mustHaveElem et
vs <- go
return (v:vs)
attContent :: AttValue -> String
attContent (AttValue xs) = foldl (flip (flip (++) . either id refToString)) [] xs
refToString (RefEntity s) = refEntityString s
refToString (RefChar i) = [toEnum i]
attrIs :: String -> String -> AttributeTester ()
attrIs k v _ (nm,attv) | v == attContent attv && k == nm = return (Just ())
| otherwise = return Nothing
hasAttr :: AttributeTester a -> AttributeFinder (Maybe a)
hasAttr at = get >>= \ (pos,attrs) -> mapM (lift . at pos) attrs >>= return . zip attrs >>= (\ ps -> case span (isNothing . snd) ps of
(bs,[]) -> return Nothing
(bs,c:cs) -> put (pos,map fst (bs ++ cs)) >> return (snd c))
thisAttr :: String -> String -> AttributesTester ()
thisAttr k v p atts = case runStateT (hasAttr (attrIs k v)) (p,atts) of
Left s -> throwError ("at " ++ show p ++ ": " ++ s)
Right (Nothing,(_,l)) -> return Nothing
Right (v,(_,[])) -> return v
_ -> throwError ("at " ++ show p ++ ": unexpected attributes")
infixr 1 <|>
(<|>) :: ElementTester a -> ElementTester a -> ElementTester a
(<|>) l r p e = case l p e of
Left s -> throwError ("at: " ++ show p ++ s)
Right Nothing -> r p e
Right v -> return v
nope :: ElementTester a
nope _ _ = return Nothing
choice :: [ElementTester a] -> ElementTester a
choice = foldl (<|>) nope
boolContent cs = simpleContent cs >>= (\ v -> case v of
"true" -> Right True
"false" -> Right False
s -> Left ("unrecognized bool " ++ s))
readableContent :: Read a => ContentAcceptor a
readableContent cs = simpleContent cs >>= readM
refEntityString "lt" = "<"
refEntityString "gt" = ">"
refEntityString "amp" = "&"
refEntityString "quot" = "\""
refEntityString "apos" = "'"
refEntityString _ = "?"
simpleContent :: ContentAcceptor String
simpleContent cs = mapM processContentItem cs >>= return . concat
where
processContentItem (CElem (Elem name _ _) _) = Left ("unexpected content element (" ++ name ++ ")")
processContentItem (CString _ s _) = Right s
processContentItem (CRef (RefEntity s) _) = Right $ refEntityString s
processContentItem (CRef (RefChar i) _) = Right $ [toEnum i]
processContentItem (CMisc _ _) = Right "unexpected content"
empty :: ContentAcceptor ()
empty [] = Right ()
empty (c:_) = Left ("unexpected content at" ++ show (info c))
----------------------
data Foo = Bar { x :: Int, y :: String, z :: Maybe Double }
| Baz { q :: String, r :: Int }
deriving Show
bar :: ContentAcceptor Foo
bar = comprises $ do
x <- mustHave "x" readableContent
y <- mustHave "y" simpleContent
z <- canHave "z" readableContent
return (Bar x y z)
baz = comprises $ do
q <- mustHave "q" simpleContent
r <- mustHave "r" readableContent
return (Baz q r)
fooE = el "BarFoo" id bar
<|> el "BazFoo" id baz
fooAs :: String -> ElementTester Foo
fooAs s = elWith s (const id) (thisAttr "class" "BarFoo") bar
<|> elWith s (const id) (thisAttr "class" "BazFoo") baz
data Zzz = Zzz { content :: [Foo], bleah :: Foo } deriving Show
zzzE = el "Zzz" id $ comprises (mustHave "content" (many fooE) >>= \ cs -> mustHaveElem (fooAs "bleah") >>= \ b -> return $ Zzz cs b)
parse :: ElementAcceptor a -> String -> Either String a
parse eaf s = eaf noPos el
where Document _ _ el _ = xmlParse "" s