ideas-0.5.8: src/Service/ModeXML.hs
-----------------------------------------------------------------------------
-- Copyright 2009, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-- Services using XML notation
--
-----------------------------------------------------------------------------
module Service.ModeXML (processXML) where
import Common.Context
import Common.Exercise
import Common.Strategy hiding (not, fail)
import Common.Transformation hiding (name, defaultArgument)
import Common.Utils (Some(..))
import Control.Monad
import Data.Char
import Data.Maybe
import Service.ExerciseList
import Service.ProblemDecomposition
import Service.Request
import Service.Revision (version)
import Service.ServiceList
import Service.TypedAbstractService hiding (exercise)
import Service.Types (Evaluator(..), Type, encodeDefault, decodeDefault, Encoder(..), Decoder(..))
import Domain.Math.Expr
import Text.OpenMath.Object
import Text.OpenMath.Reply (replyToXML)
import Text.OpenMath.Request (xmlToRequest)
import Text.XML
import qualified Common.Transformation as Rule
import qualified Service.Types as Tp
processXML :: String -> IO (Request, String, String)
processXML input =
either fail return $ do
xml <- parseXML input
req <- xmlRequest xml
out <- xmlRequestHandler xml
return (req, showXML out, "application/xml")
xmlRequest :: XML -> Either String Request
xmlRequest xml = do
unless (name xml == "request") $
fail "expected xml tag request"
srv <- findAttribute "service" xml
let code = extractExerciseCode xml
enc <- case findAttribute "encoding" xml of
Just s -> liftM Just (readEncoding s)
Nothing -> return Nothing
return Request
{ service = srv
, exerciseID = code
, source = findAttribute "source" xml
, dataformat = XML
, encoding = enc
}
xmlReply :: Request -> XML -> Either String XML
xmlReply request xml
| service request == "mathdox" = do
code <- maybe (fail "unknown exercise code") return (exerciseID request)
OMEX ex <- getOpenMathExercise code
(st, sloc, answer) <- xmlToRequest xml ex
return (replyToXML (problemDecomposition st sloc answer))
| otherwise =
case encoding request of
Just StringEncoding -> do
code <- maybe (fail "unknown exercise code") return (exerciseID request)
ex <- getExercise code
case stringFormatConverter ex of
Some conv -> do
srv <- getService (service request)
res <- evalService conv srv xml
return (resultOk res)
_ -> do
code <- maybe (fail "unknown exercise code") return (exerciseID request)
ex <- getOpenMathExercise code
case openMathConverter ex of
Some conv -> do
srv <- getService (service request)
res <- evalService conv srv xml
return (resultOk res)
xmlRequestHandler :: Monad m => XML -> m XML
xmlRequestHandler xml =
case xmlRequest xml of
Left err -> return (resultError err)
Right request ->
case xmlReply request xml of
Left err -> return (resultError err)
Right result -> return result
extractExerciseCode :: Monad m => XML -> m ExerciseCode
extractExerciseCode xml =
case liftM (break (== '.')) (findAttribute "exerciseid" xml) of
Just (as, _:bs) -> return (makeCode as bs)
Just (as, _) -> resolveExerciseCode as
-- being backwards compatible with early MathDox
Nothing ->
case fmap getData (findChild "strategy" xml) of
Just name ->
let s ~= t = f s == f t
f = map toLower . filter isAlphaNum
in case findOpenMathExercises (\ex -> name ~= description ex) of
[OMEX a] -> return (exerciseCode a)
_ -> fail $ "Unknown strategy name " ++ show name
_ -> fail "no exerciseid attribute, nor a known strategy element"
resultOk :: XMLBuilder -> XML
resultOk body = makeXML "reply" $ do
"result" .=. "ok"
"version" .=. version
body
resultError :: String -> XML
resultError txt = makeXML "reply" $ do
"result" .=. "error"
"version" .=. version
element "message" (text txt)
------------------------------------------------------------
-- Mixing abstract syntax (OpenMath format) and concrete syntax (string)
stringFormatConverter :: Some Exercise -> Some (Evaluator (Either String) XML XMLBuilder)
stringFormatConverter (Some ex) =
Some $ Evaluator (xmlEncoder f ex) (xmlDecoder g ex)
where
f = return . element "expr" . text . prettyPrinter ex
g xml = do
xml <- findChild "expr" xml -- quick fix
-- guard (name xml == "expr")
let input = getData xml
either (fail . show) return (parser ex input)
openMathConverter :: OpenMathExercise -> Some (Evaluator (Either String) XML XMLBuilder)
openMathConverter (OMEX ex) =
Some $ Evaluator (xmlEncoder f ex) (xmlDecoder g ex)
where
f = return . builder . toXML . toOMOBJ . toExpr
g xml = do
xob <- findChild "OMOBJ" xml
omobj <- xml2omobj xob
case fromExpr (fromOMOBJ omobj) of
Just a -> return a
Nothing -> fail "Unknown OpenMath object"
xmlEncoder :: Monad m => (a -> m XMLBuilder) -> Exercise a -> Encoder m XMLBuilder a
xmlEncoder f ex = Encoder
{ encodeType = encode (xmlEncoder f ex)
, encodeTerm = f
, encodeTuple = sequence_
}
where
encode :: Monad m => Encoder m XMLBuilder a -> Type a t -> t -> m XMLBuilder
encode enc serviceType =
case serviceType of
Tp.List t1 -> \xs -> do
bs <- mapM (encode enc t1) xs
let b = mapM_ (element "elem") bs
return (element "list" b)
Tp.Elem t1 -> liftM (element "elem") . encode enc t1
Tp.Tag s t1 -> liftM (element s) . encode enc t1 -- quick fix
Tp.Rule -> return . ("ruleid" .=.) . Rule.name
Tp.Term -> encodeTerm enc . fromContext
Tp.Location -> return . text . show
Tp.Bool -> return . text . show
Tp.Int -> return . text . show
Tp.State -> encodeState (encodeTerm enc)
_ -> encodeDefault enc serviceType
xmlDecoder :: MonadPlus m => (XML -> m a) -> Exercise a -> Decoder m XML a
xmlDecoder f ex = Decoder
{ decodeType = decode (xmlDecoder f ex)
, decodeTerm = f
, decoderExercise = ex
}
where
decode :: MonadPlus m => Decoder m XML a -> Type a t -> XML -> m (t, XML)
decode dec serviceType =
case serviceType of
Tp.State -> decodeState (decoderExercise dec) (decodeTerm dec)
Tp.Location -> leave $ liftM (read . getData) . findChild "location"
Tp.Rule -> leave $ fromMaybe (fail "unknown rule") . liftM (getRule (decoderExercise dec) . getData) . findChild "ruleid"
Tp.Exercise -> leave $ const (return (decoderExercise dec))
Tp.Term -> \xml -> decodeTerm dec xml >>= \a -> return (inContext a, xml)
_ -> decodeDefault dec serviceType
leave :: Monad m => (XML -> m a) -> XML -> m (a, XML)
leave f xml = liftM (\a -> (a, xml)) (f xml)
decodeState :: Monad m => Exercise a -> (XML -> m a) -> XML -> m (State a, XML)
decodeState ex f top = do
xml <- findChild "state" top
unless (name xml == "state") (fail "expected a state tag")
let sp = maybe "[]" getData (findChild "prefix" xml)
sc = maybe "" getData (findChild "context" xml)
--x <- findChild "OMOBJ" xml
expr <- f xml
contxt <- maybe (fail $ "invalid context" ++ show sc) return (parseContext sc)
let state = State ex (Just (makePrefix (read sp) $ strategy ex)) term
term = fmap (const expr) contxt
return (state, top)
encodeState :: Monad m => (a -> m XMLBuilder) -> State a -> m XMLBuilder
encodeState f state = do
b <- f (term state)
return $ element "state" $ do
element "prefix" (text $ maybe "[]" show (prefix state))
element "context" (text $ showContext (context state))
b