packages feed

haskell-xmpp-2.0.0: src/Network/XMPP/XEP/Form.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}
{-# LANGUAGE DataKinds         #-}

-----------------------------------------------------------------------------
-- |
-- Copyright   :  (c) riskbook, 2020
-- SPDX-License-Identifier:  BSD3
--
-----------------------------------------------------------------------------
module Network.XMPP.XEP.Form where

import           Text.Hamlet.XML             (xml)
import           Text.XML.HaXml.Xtract.Parse (xtract)

import           Data.Maybe
import           Data.List                   (find)
import qualified Data.Text                   as T

import           Network.XMPP.XML

-- Specification:
-- https://xmpp.org/extensions/xep-0004.html#table-2
--

-- https://xmpp.org/extensions/xep-0004.html#table-2
instance FromXML XmppField where
  decodeXml m =
    let _label   = txtpat "/field/@label" m
        typ      = txtpat "/field/@type" m
        variable = txtpat "/field/@var" m
    in  case typ of
          "boolean"     -> BooleanField variable <$> boolVal
          "text-single" -> Just $ SingleTextField variable txtSingleVal
          "list-single" ->
            Just $ ListSingleField variable listOptions txtSingleVal
          "list-multi" -> Just $ ListMultiField variable listOptions listValues
          "hidden"     -> Just $ HiddenField variable txtSingleVal
          _            -> Nothing
    where
      listValues   = txtpat "/value/-" <$> xtract id "/field/value/" m
      listOptions  = txtpat "/value/-" <$> xtract id "/field/option/value" m
      txtSingleVal = txtpat "/field/value/-" m
      boolVal      = case txtpat "/field/value/-" m of
        "0" -> Just False
        "1" -> Just True
        _   -> Nothing


newtype XmppForm = XmppForm [XmppField] deriving (Eq, Show)

type FieldName = T.Text

data XmppField =
    SingleTextField
    { xfName  :: FieldName
    , stfValue :: T.Text
    }
  | ListSingleField
    { xfName    :: FieldName
    , lsfOptions :: [T.Text]
    , lsfValue   :: T.Text
    }
  | BooleanField
    { xfName  :: FieldName
    , bfValue :: Bool
    }
  | ListMultiField
    { xfName    ::FieldName
    , lmfOptions :: [T.Text]
    , lmfValue   :: [T.Text]
    }
  | HiddenField { xfName :: T.Text, hfValue :: T.Text }
  deriving (Eq, Show)

updateFormField :: FieldName -> (XmppField -> XmppField) -> XmppForm -> XmppForm
updateFormField fname update (XmppForm fields) =
  let mField = update <$> find ((== fname) . xfName) fields
      nextFields =
          (<> maybeToList mField) . filter ((/= fname) . xfName) $ fields
  in  XmppForm nextFields

setBoolValue :: Bool -> XmppField -> XmppField
setBoolValue val (BooleanField name _) = BooleanField name val
setBoolValue _ field = field

instance FromXML XmppForm where
  decodeXml = Just . XmppForm . mapMaybe decodeXml . xtract id "/x/field"

instance ToXML XmppForm where
  encodeXml (XmppForm fields) =
    [xml|
      <x xmlns="jabber:x:data" type="submit">
        $forall field <- fields
          $case field
            $of HiddenField name value
              <field var=#{name}>
                <value>#{value}

            $of SingleTextField name value
              <field var=#{name}>
                <value>#{value}

            $of BooleanField name value
              <field var=#{name}>
                <value>
                  $if value
                    1
                  $else
                    0

            $of ListSingleField name _opts value
              <field var=#{name}>
                <value>#{value}

            $of ListMultiField name _opts values
              <field var=#{name}>
                $forall value <- values
                  <value>#{value}
    |]