5 changed files with 360 additions and 4 deletions
@ -0,0 +1,170 @@ |
|||||||
|
{-# Language OverloadedStrings, ViewPatterns, NoMonomorphismRestriction #-} |
||||||
|
|
||||||
|
module Network.XMPP.Marshal where |
||||||
|
|
||||||
|
import Control.Applicative((<$>)) |
||||||
|
|
||||||
|
import Control.Monad.State |
||||||
|
|
||||||
|
import Data.Maybe |
||||||
|
|
||||||
|
import qualified Data.Text as Text |
||||||
|
import Data.XML.Types |
||||||
|
|
||||||
|
import Network.XMPP.Types |
||||||
|
|
||||||
|
|
||||||
|
-- create attribute from Just |
||||||
|
matr _ Nothing = [] |
||||||
|
matr n (Just x) = [(n,x)] |
||||||
|
|
||||||
|
-- Child if text is not empty |
||||||
|
nech _ "" = [] |
||||||
|
nech n x = [ NodeElement (Element n [] [NodeContent (ContentText x) ]) ] |
||||||
|
|
||||||
|
-- Child if text is not Nothing |
||||||
|
mnech _ Nothing = [] |
||||||
|
mnech n (Just x) = [ NodeElement (Element n [] [NodeContent (ContentText x) ]) ] |
||||||
|
|
||||||
|
-- make Attributes from text |
||||||
|
contentify (x,y) = (x, [ContentText y]) |
||||||
|
|
||||||
|
-- Marshal Message to XML Element |
||||||
|
messageToElement (Message from to ident tp sub body thread exts) = |
||||||
|
Element "message" |
||||||
|
(map contentify . concat $ |
||||||
|
[ matr "from" (toText <$> from) |
||||||
|
, [("to", toText to)] |
||||||
|
, matr "id" ident |
||||||
|
, [("type", toText tp)] |
||||||
|
]) |
||||||
|
(concat $ |
||||||
|
[ mnech "subject" sub |
||||||
|
, mnech "body" body |
||||||
|
, mnech "thread" thread |
||||||
|
, map NodeElement exts |
||||||
|
]) |
||||||
|
|
||||||
|
-- Marshal XML element to message |
||||||
|
elementToMessage e@(Element "message" _ _) = |
||||||
|
let from = fromText <$> attributeText "from" e |
||||||
|
Just to = fromText <$> attributeText "to" e |
||||||
|
ident = attributeText "id" e |
||||||
|
Just tp = fromText <$> attributeText "type" e |
||||||
|
-- Oh dear, this is HORRIBLE. TODO: come up with something sane |
||||||
|
in grabFrom (elementChildren e) $ do |
||||||
|
-- TODO multiple bodies (different languages) |
||||||
|
body <- maybeGrabNamed "body" |
||||||
|
-- TODO multiple subjects (different languages) |
||||||
|
subject <- maybeGrabNamed "subject" |
||||||
|
thread <- maybeGrabNamed "thread" |
||||||
|
ext <- grabRest |
||||||
|
return $ Message |
||||||
|
from |
||||||
|
to |
||||||
|
ident |
||||||
|
tp |
||||||
|
(elementToText <$>subject) |
||||||
|
(elementToText <$> body) |
||||||
|
(elementToText <$> thread) |
||||||
|
ext |
||||||
|
|
||||||
|
presenceTOXML (Presence from to id tp stp stat pri exts) = |
||||||
|
Element "message" |
||||||
|
(map contentify . concat $ |
||||||
|
[ matr "from" (toText <$> from) |
||||||
|
, matr "to" (toText <$> to) |
||||||
|
, matr "id" id |
||||||
|
, matr "type" ( toText <$> tp) |
||||||
|
]) |
||||||
|
(concat $ |
||||||
|
[ mnech "show" (toText <$> stp) |
||||||
|
, mnech "status" stat |
||||||
|
, mnech "priority" (Text.pack . show <$> pri) |
||||||
|
, map NodeElement exts |
||||||
|
]) |
||||||
|
|
||||||
|
-- Marshal XML element to message |
||||||
|
elementToPresence e@(Element "message" _ _) = |
||||||
|
let from = fromText <$> attributeText "from" e |
||||||
|
to = fromText <$> attributeText "to" e |
||||||
|
ident = attributeText "id" e |
||||||
|
tp = fromText <$> attributeText "type" e |
||||||
|
in grabFrom (elementChildren e) $ do |
||||||
|
pshow <- maybeGrabNamed "show" |
||||||
|
-- TODO multiple status (different languages) |
||||||
|
stat <- maybeGrabNamed "status" |
||||||
|
prio <- maybeGrabNamed "priority" |
||||||
|
ext <- grabRest |
||||||
|
return $ Presence |
||||||
|
from |
||||||
|
to |
||||||
|
ident |
||||||
|
tp |
||||||
|
(fromText . elementToText <$> pshow) |
||||||
|
(elementToText <$> stat) |
||||||
|
(read . Text.unpack . elementToText <$> prio) |
||||||
|
ext |
||||||
|
|
||||||
|
|
||||||
|
iqToElement (IQ from to id tp body) = |
||||||
|
Element "message" |
||||||
|
(map contentify . concat $ |
||||||
|
[ matr "from" (toText <$> from) |
||||||
|
, matr "to" (toText <$> to ) |
||||||
|
, [("id" , id)] |
||||||
|
, [("type", toText tp)] |
||||||
|
]) |
||||||
|
[ NodeElement body ] |
||||||
|
|
||||||
|
elementToIQ e@(Element "iq" _ _) = |
||||||
|
let from = fromText <$> attributeText "from" e |
||||||
|
to = fromText <$> attributeText "to" e |
||||||
|
Just ident= attributeText "id" e |
||||||
|
Just tp = fromText <$> attributeText "type" e |
||||||
|
[ext] = elementChildren e |
||||||
|
in IQ |
||||||
|
from |
||||||
|
to |
||||||
|
ident |
||||||
|
tp |
||||||
|
ext |
||||||
|
|
||||||
|
-- take and remove all elements matching a predicate from the list |
||||||
|
takeAllFromList pred l = let (l', xs) = go pred [] l in (reverse l', xs) |
||||||
|
where |
||||||
|
go pred ys [] = (ys, []) |
||||||
|
go pred ys (x:xs) = |
||||||
|
case pred x of |
||||||
|
True -> let (ys', rs) = go pred ys xs in (ys', x:rs) |
||||||
|
False -> go pred (x:ys) xs |
||||||
|
|
||||||
|
-- The "Grab Monad" : sucessively take and remove ("grab") |
||||||
|
-- elements from a "pool" (list) |
||||||
|
|
||||||
|
-- Put a list of elements into the pool and start grabbing |
||||||
|
grabFrom l = flip runState l |
||||||
|
|
||||||
|
-- grab all elements matching predicate out of the pool |
||||||
|
grabAll p = do |
||||||
|
l <- get |
||||||
|
let (l', xs) = takeAllFromList p l |
||||||
|
put l' |
||||||
|
return xs |
||||||
|
|
||||||
|
-- grab XML-elements by exact name |
||||||
|
grabNamed = grabAll . hasName |
||||||
|
|
||||||
|
-- This throws away all elements after the first one |
||||||
|
-- TODO: Be more stricy here |
||||||
|
maybeGrabNamed = liftM listToMaybe . grabAll . hasName |
||||||
|
|
||||||
|
-- grab all remaining elements from the pool |
||||||
|
grabRest = do |
||||||
|
l <- get |
||||||
|
put [] |
||||||
|
return l |
||||||
|
|
||||||
|
hasName x e = x == elementName e |
||||||
|
|
||||||
|
elementToText = Text.concat . elementText |
||||||
@ -0,0 +1,181 @@ |
|||||||
|
{-# LANGUAGE OverloadedStrings #-} |
||||||
|
|
||||||
|
module Network.XMPP.Types where |
||||||
|
-- proudly "borrowed" from haskell-xmpp |
||||||
|
|
||||||
|
import Control.Applicative((<$>)) |
||||||
|
import Control.Monad |
||||||
|
|
||||||
|
import Data.Maybe |
||||||
|
import Data.Text as Text |
||||||
|
import Data.String as Str |
||||||
|
import Data.XML.Types |
||||||
|
|
||||||
|
class ToText a where |
||||||
|
toText :: a -> Text |
||||||
|
|
||||||
|
class FromText a where |
||||||
|
fromText :: Text -> a |
||||||
|
|
||||||
|
-- | Jabber ID (JID) datatype |
||||||
|
data JID = JID { node :: Maybe Text |
||||||
|
-- ^ Account name |
||||||
|
, domain :: Text |
||||||
|
-- ^ Server adress |
||||||
|
, resource :: Maybe Text |
||||||
|
-- ^ Resource name |
||||||
|
} |
||||||
|
instance ToText JID where |
||||||
|
toText (JID n d r) = |
||||||
|
let n' = maybe "" (`append` "@" ) n |
||||||
|
r' = maybe "" ("/" `append` ) r |
||||||
|
in Text.concat [n', d, r'] |
||||||
|
|
||||||
|
instance FromText JID where |
||||||
|
fromText = parseJID |
||||||
|
|
||||||
|
instance Show JID where |
||||||
|
show = Text.unpack . toText |
||||||
|
|
||||||
|
-- Ugh, that smells a bit. |
||||||
|
parseJID jid = |
||||||
|
let (jid', rst) = case Text.splitOn "@" jid of |
||||||
|
[rest] -> (JID Nothing, rest) |
||||||
|
[node,rest] -> (JID (Just node), rest) |
||||||
|
_ -> error $ "Couldn't parse JID: \"" ++ Text.unpack jid ++ "\"" |
||||||
|
in case Text.splitOn "/" rst of |
||||||
|
[domain] -> jid' domain Nothing |
||||||
|
[domain, resource] -> jid' domain (Just resource) |
||||||
|
_ -> error $ "Couldn't parse JID: \"" ++ Text.unpack jid ++ "\"" |
||||||
|
|
||||||
|
instance IsString JID where |
||||||
|
fromString = parseJID . Text.pack |
||||||
|
|
||||||
|
|
||||||
|
-- should we factor from, to and id out, even though they are |
||||||
|
-- sometimes mandatory? |
||||||
|
data Message = Message |
||||||
|
{ mFrom :: Maybe JID |
||||||
|
, mTo :: JID |
||||||
|
, mId :: Maybe Text |
||||||
|
-- ^ Message 'from', 'to', 'id' attributes |
||||||
|
, mType :: MessageType |
||||||
|
-- ^ Message type (2.1.1) |
||||||
|
, mSubject :: Maybe Text |
||||||
|
-- ^ Subject element (2.1.2.1) |
||||||
|
, mBody :: Maybe Text |
||||||
|
-- ^ Body element (2.1.2.2) |
||||||
|
, mThread :: Maybe Text |
||||||
|
-- ^ Thread element (2.1.2.3) |
||||||
|
, mExt :: [Element] |
||||||
|
-- ^ Additional contents, used for extensions |
||||||
|
} deriving Show |
||||||
|
|
||||||
|
data Presence = Presence |
||||||
|
{ pFrom :: Maybe JID |
||||||
|
, pTo :: Maybe JID |
||||||
|
, pId :: Maybe Text |
||||||
|
-- ^ Presence 'from', 'to', 'id' attributes |
||||||
|
, pType :: Maybe PresenceType |
||||||
|
-- ^ Presence type (2.2.1) |
||||||
|
, pShowType :: Maybe ShowType |
||||||
|
-- ^ Show element (2.2.2.1) |
||||||
|
, pStatus :: Maybe Text |
||||||
|
-- ^ Status element (2.2.2.2) |
||||||
|
, pPriority :: Maybe Int |
||||||
|
-- ^ Presence priority (2.2.2.3) |
||||||
|
, pExt :: [Element] |
||||||
|
-- ^ Additional contents, used for extensions |
||||||
|
} |
||||||
|
|
||||||
|
data IQ = IQ |
||||||
|
{ iqFrom :: Maybe JID |
||||||
|
, iqTo :: Maybe JID |
||||||
|
, iqId :: Text |
||||||
|
-- ^ IQ id (Core-9.2.3) |
||||||
|
, iqType :: IQType |
||||||
|
-- ^ IQ type (Core-9.2.3) |
||||||
|
, iqBody :: Element |
||||||
|
-- ^ Child element (Core-9.2.3) |
||||||
|
} |
||||||
|
|
||||||
|
data Stanza = SMessage Message | SPresence Presence | SIQ IQ -- deriving Show |
||||||
|
|
||||||
|
data MessageType = Chat | GroupChat | Headline | Normal | MessageError deriving (Eq, Show) |
||||||
|
|
||||||
|
data PresenceType = Default | Unavailable | Subscribe | Subscribed | Unsubscribe | Unsubscribed | Probe | PresenceError deriving Eq |
||||||
|
|
||||||
|
data IQType = Get | Result | Set | IQError deriving Eq |
||||||
|
|
||||||
|
data ShowType = Available | Away | FreeChat | DND | XAway deriving Eq |
||||||
|
|
||||||
|
instance ToText MessageType where |
||||||
|
toText Chat = "chat" |
||||||
|
toText GroupChat = "groupchat" |
||||||
|
toText Headline = "headline" |
||||||
|
toText Normal = "normal" |
||||||
|
toText MessageError = "error" |
||||||
|
|
||||||
|
instance ToText PresenceType where |
||||||
|
toText Default = "" |
||||||
|
toText Unavailable = "unavailable" |
||||||
|
toText Subscribe = "subscribe" |
||||||
|
toText Subscribed = "subscribed" |
||||||
|
toText Unsubscribe = "unsubscribe" |
||||||
|
toText Unsubscribed = "unsubscribed" |
||||||
|
toText Probe = "probe" |
||||||
|
toText PresenceError = "error" |
||||||
|
|
||||||
|
instance ToText IQType where |
||||||
|
toText Get = "get" |
||||||
|
toText Result = "result" |
||||||
|
toText Set = "set" |
||||||
|
toText IQError = "error" |
||||||
|
|
||||||
|
instance ToText ShowType where |
||||||
|
toText Available = "" |
||||||
|
toText Away = "away" |
||||||
|
toText FreeChat = "chat" |
||||||
|
toText DND = "dnd" |
||||||
|
toText XAway = "xa" |
||||||
|
|
||||||
|
|
||||||
|
instance FromText MessageType where |
||||||
|
fromText "chat" = Chat |
||||||
|
fromText "groupchat" = GroupChat |
||||||
|
fromText "headline" = Headline |
||||||
|
fromText "normal" = Normal |
||||||
|
fromText "error" = MessageError |
||||||
|
fromText "" = Chat |
||||||
|
fromText _ = error "incorrect message type" |
||||||
|
|
||||||
|
instance FromText PresenceType where |
||||||
|
fromText "" = Default |
||||||
|
fromText "available" = Default |
||||||
|
fromText "unavailable" = Unavailable |
||||||
|
fromText "subscribe" = Subscribe |
||||||
|
fromText "subscribed" = Subscribed |
||||||
|
fromText "unsubscribe" = Unsubscribe |
||||||
|
fromText "unsubscribed" = Unsubscribed |
||||||
|
fromText "probe" = Probe |
||||||
|
fromText "error" = PresenceError |
||||||
|
fromText _ = error "incorrect presence type" |
||||||
|
|
||||||
|
instance FromText IQType where |
||||||
|
fromText "get" = Get |
||||||
|
fromText "result" = Result |
||||||
|
fromText "set" = Set |
||||||
|
fromText "error" = IQError |
||||||
|
fromText "" = Get |
||||||
|
fromText _ = error "incorrect iq type" |
||||||
|
|
||||||
|
instance FromText ShowType where |
||||||
|
fromText "" = Available |
||||||
|
fromText "available" = Available |
||||||
|
fromText "away" = Away |
||||||
|
fromText "chat" = FreeChat |
||||||
|
fromText "dnd" = DND |
||||||
|
fromText "xa" = XAway |
||||||
|
fromText "invisible" = Available |
||||||
|
fromText _ = error "incorrect <show> value" |
||||||
|
|
||||||
Loading…
Reference in new issue