module Database.HaSparqlClient.Queries (runQuery, runSelectQuery, runAskQuery) where
import Network.URI
import Network.HTTP
import Text.XML.Light
import Data.Maybe
import Control.Monad (guard)
import Data.Char (toLower)
import Database.HaSparqlClient.Types
import Database.HaSparqlClient.Values
runQuery :: Service -> Method -> IO(Either String String)
runQuery = getSparqlRequest right . constructURI
runSelectQuery :: Service -> Method -> IO(Either String [[BindingValue]])
runSelectQuery = getSparqlRequest (parse $ parseSparqlVariables >>= parseSparqlResults) . constructURI
runAskQuery :: Service -> Method -> IO(Either String Bool)
runAskQuery serv m= do
b <- getSparqlRequest (parse parseSparqlBooleanResult) (constructURI serv) m
case b of
Right x -> case x of
Just True -> return $ Right True
Just False -> return $ Right False
_ -> return $ Left "Boolean binding not found."
Left x -> return $ Left x
getSparqlRequest :: (String -> Either String b) -> Either String (URI,[ExtraParameters]) -> Method -> IO (Either String b)
getSparqlRequest f u m = case u of
Left err -> return $ Left err
Right uri -> do
resp <- getRespBody uri m
case resp of
Left err -> return $ Left (show err)
Right rsp -> case rspCode rsp of
(2,_,_) -> do
let xml = rspBody rsp
return $ f xml
(a,b,c) -> do
let rspErr = rspBody rsp
return $ Left rspErr
constructURI :: Service -> Either String (URI,[ExtraParameters])
constructURI (Sparql endpoint query defg ng oth) = case parseURI endpoint of
Nothing -> Left "Bad string for endpoint."
Just uri -> Right (uri,[("query",query)] ++ defaultgraph defg++ othervars ng ++ filtparams oth)
where
othervars lst = [("named-graph-uri",x)|x<-lst]
defaultgraph dg = case dg of
Nothing -> []
Just g -> [("default-graph-uri", g)]
filtparams = filter bool
bool (a,b)
|lower a /= "named-graph-uri" && lower a /= "default-graph-uri" = True
|otherwise = False
lower = map toLower
quri :: Maybe String
quri = Just "http://www.w3.org/2005/sparql-results#"
parseSparqlVariables :: Element -> [String]
parseSparqlVariables doc = mapMaybe attr (findElements (QName "variable" quri Nothing) doc)
where
attr = findAttr (QName "name" Nothing Nothing)
parseSparqlResults :: [String] -> Element -> [[BindingValue]]
parseSparqlResults vars = map (parseSparqlBindings vars) . findElements (QName "result" quri Nothing)
parseSparqlBindings :: [String] -> Element -> [BindingValue]
parseSparqlBindings vars doc = map pVar vars
where
pVar v = maybe Unbound (elementBinding . head . elChildren) $ filterElement (pred v) doc
pred v e = isJust $ do
a <- findAttr (unqual "name") e
guard $ a == v
parseSparqlBooleanResult :: Element -> Maybe Bool
parseSparqlBooleanResult doc = case (findElement (QName "boolean" quri Nothing) doc) of
Just elem -> case (strContent elem) of
"true" -> Just True
"false" -> Just False
_ -> Nothing
elementBinding :: Element -> BindingValue
elementBinding e = case qName (elName e) of
"uri" -> Database.HaSparqlClient.Types.URI (strContent e)
"literal" -> case findAttr (unqual "datatype") e of
Just dt -> TypedLiteral (strContent e) dt
Nothing -> case findAttr langAttr e of
Just lang -> LangLiteral (strContent e) lang
Nothing -> Literal (strContent e)
"bnode" -> BNode (strContent e)
_ -> Unbound
where
langAttr = blank_name {qName = "lang", qPrefix = Just "xml"}
getRespBody :: (URI,[ExtraParameters]) -> Method -> IO (Either IOError (Response String))
getRespBody u m = catch (simpleHTTP(mountRequest m u) >>= (\(Right rsp) -> return (Right rsp))) (return . Left )
mountRequest m (uri,params) = case m of
HPOST -> (Request uri POST [mkHeader HdrContentType "application/x-www-form-urlencoded", mkHeader HdrAccept accept, mkHeader HdrContentLength (show $ length $ urlEncodeVars params), mkHeader HdrUserAgent "hasparql-client-0.1"] (urlEncodeVars params))
_ -> insertHeaders [mkHeader HdrAccept accept] (getRequest $ show uri ++ "?" ++ urlEncodeVars params)
parse f s = case parseXMLDoc s of
Just doc -> Right (f doc)
Nothing -> Left "Internal error parsing xml results."
right :: b -> Either a b
right = Right
accept = "application/sparql-results+xml, application/rdf+xml, */*"