{-# LANGUAGE OverloadedStrings #-} module DBus.Introspection.Parse ( parseXML ) where import Conduit import Data.Maybe import Data.XML.Types import qualified Data.Text as T import qualified Text.XML.Stream.Parse as X import DBus.Internal.Types import DBus.Introspection.Types data ObjectChildren = InterfaceDefinition Interface | SubNode Object data InterfaceChildren = MethodDefinition Method | SignalDefinition Signal | PropertyDefinition Property parseXML :: ObjectPath -> T.Text -> Maybe Object parseXML path xml = runConduit $ yieldMany [xml] .| X.parseText X.def .| X.force "parse error" (parseObject $ getRootName path) getRootName :: ObjectPath -> X.AttrParser ObjectPath getRootName defaultPath = do nodeName <- X.attr "name" pure $ maybe defaultPath (objectPath_ . T.unpack) nodeName getChildName :: ObjectPath -> X.AttrParser ObjectPath getChildName parentPath = do nodeName <- X.requireAttr "name" let parentPath' = case formatObjectPath parentPath of "/" -> "/" x -> x ++ "/" pure $ objectPath_ (parentPath' ++ T.unpack nodeName) parseObject :: X.AttrParser ObjectPath -> ConduitT Event o Maybe (Maybe Object) parseObject getPath = X.tag' "node" getPath parseContent where parseContent objPath = do elems <- X.many $ X.choose [ fmap SubNode <$> parseObject (getChildName objPath) , fmap InterfaceDefinition <$> parseInterface ] let base = Object objPath [] [] addElem e (Object p is cs) = case e of InterfaceDefinition i -> Object p (i:is) cs SubNode c -> Object p is (c:cs) pure $ foldr addElem base elems parseInterface :: ConduitT Event o Maybe (Maybe Interface) parseInterface = X.tag' "interface" getName parseContent where getName = do ifName <- X.requireAttr "name" pure $ interfaceName_ (T.unpack ifName) parseContent ifName = do elems <- X.many $ do X.many_ $ X.ignoreTreeContent "annotation" X.ignoreAttrs X.choose [ parseMethod , parseSignal , parseProperty ] X.many_ $ X.ignoreTreeContent "annotation" X.ignoreAttrs let base = Interface ifName [] [] [] addElem e (Interface n ms ss ps) = case e of MethodDefinition m -> Interface n (m:ms) ss ps SignalDefinition s -> Interface n ms (s:ss) ps PropertyDefinition p -> Interface n ms ss (p:ps) pure $ foldr addElem base elems parseMethod :: ConduitT Event o Maybe (Maybe InterfaceChildren) parseMethod = X.tag' "method" getName parseArgs where getName = do ifName <- X.requireAttr "name" parseMemberName (T.unpack ifName) parseArgs name = do args <- X.many $ do X.many_ $ X.ignoreTreeContent "annotation" X.ignoreAttrs X.tag' "arg" getArg pure X.many_ $ X.ignoreTreeContent "annotation" X.ignoreAttrs pure $ MethodDefinition $ Method name args getArg = do name <- fromMaybe "" <$> X.attr "name" typeStr <- X.requireAttr "type" dirStr <- fromMaybe "in" <$> X.attr "direction" X.ignoreAttrs typ <- parseType typeStr let dir = if dirStr == "in" then In else Out pure $ MethodArg (T.unpack name) typ dir parseSignal :: ConduitT Event o Maybe (Maybe InterfaceChildren) parseSignal = X.tag' "signal" getName parseArgs where getName = do ifName <- X.requireAttr "name" parseMemberName (T.unpack ifName) parseArgs name = do args <- X.many $ do X.many_ $ X.ignoreTreeContent "annotation" X.ignoreAttrs X.tag' "arg" getArg pure X.many_ $ X.ignoreTreeContent "annotation" X.ignoreAttrs pure $ SignalDefinition $ Signal name args getArg = do name <- fromMaybe "" <$> X.attr "name" typeStr <- X.requireAttr "type" X.ignoreAttrs typ <- parseType typeStr pure $ SignalArg (T.unpack name) typ parseProperty :: ConduitT Event o Maybe (Maybe InterfaceChildren) parseProperty = X.tag' "property" getProp $ \p -> do X.many_ X.ignoreAnyTreeContent pure p where getProp = do name <- T.unpack <$> X.requireAttr "name" typeStr <- X.requireAttr "type" accessStr <- fromMaybe "" <$> X.attr "access" X.ignoreAttrs typ <- parseType typeStr (canRead, canWrite) <- case accessStr of "" -> pure (False, False) "read" -> pure (True, False) "write" -> pure (False, True) "readwrite" -> pure (True, True) _ -> throwM $ userError "invalid access value" pure $ PropertyDefinition $ Property name typ canRead canWrite parseType :: MonadThrow m => T.Text -> m Type parseType typeStr = do typ <- parseSignature (T.unpack typeStr) case signatureTypes typ of [t] -> pure t _ -> throwM $ userError "invalid type sig"