~alcinnz/amphiarao

ref: a3014db3a647dab65d218e8fbd4c12b48df9a054 amphiarao/src/Internal/Forms.hs -rw-r--r-- 4.8 KiB
a3014db3 — Adrian Cochrane Add support for clicking <datalist> <options>s or <label>s. 3 years ago
                                                                                
783fd3b7 Adrian Cochrane
67fedfd0 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
67fedfd0 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
783fd3b7 Adrian Cochrane
c2bd1a21 Adrian Cochrane
67fedfd0 Adrian Cochrane
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
{-# LANGUAGE OverloadedStrings #-}
module Internal.Forms(findForm, prefillForm, modifyForm, formAction) where

import Text.XML
import Text.XML.Cursor
import qualified Data.Map as M
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as Txt

import Internal (Session, Session'(..))
import Network.URI (URI(..), parseURIReference, relativeTo, nullURI)

import Data.Maybe
import Control.Concurrent.MVar

findForm :: Cursor -> HM.HashMap Txt.Text Cursor -> Element
findForm cursor id2els
    | NodeElement el@(Element (Name "form" _ _) _ _) <- node cursor = el
    | NodeElement el <- node cursor, Just el' <- findForm' el id2els = el'
    | (form:_) <- parent cursor = findForm form id2els -- Propagate id2els for the sake of event dispatch.
    | otherwise = Element "fail" M.empty [] -- Tombstone value

findForm' :: Element -> HM.HashMap Txt.Text Cursor -> Maybe Element
findForm' (Element (Name name _ _) attrs _) id2els
    | name == "label", Just for <- "for" `M.lookup` attrs',
            Just input <- for `HM.lookup` id2els = Just $ findForm input id2els
    | Just form <- "form" `M.lookup` attrs', Just cursor <- form `HM.lookup` id2els,
        NodeElement ret <- node cursor = Just ret
    | otherwise = Nothing
  where attrs' = M.mapKeys nameLocalName attrs

prefillForm (Element (Name "form" _ _) attrs children) root = HM.fromListWith (++) (
    (prefillForm' $ map node $ (descendant >=> checkElement isForForm) root) ++
    prefillForm' children)
  where
    isForForm (Element _ attrs' _) | Just id <- "id" `M.lookup` M.mapKeys nameLocalName attrs,
        Just form <- "form" `M.lookup` M.mapKeys nameLocalName attrs' = id == form
    isForForm _ = False
prefillForm _ _ = HM.empty

prefillForm' (NodeElement (Element (Name "input" _ _) attrs _):nodes)
    | Just type_ <- "type" `M.lookup` attrs', Just name <- "name" `M.lookup` attrs', Just value <- "value" `M.lookup` attrs',
            type_ `notElem` ["radio", "checkbox", "reset", "submit", "button"] || "checked" `M.member` attrs' =
        (name, [value]):prefillForm' nodes
  where attrs' = M.mapKeys nameLocalName attrs
prefillForm' (NodeElement (Element (Name "select" _ _) attrs childs):nodes)
    | Just name <- "name" `M.lookup` attrs' =
        case [value | NodeElement (Element _ attrs2 _) <- childs,
                value <- maybeToList $ M.lookup "value" $ M.mapKeys nameLocalName attrs2] of
            [] -> prefillForm' nodes
            values | "multiple" `M.member` attrs' -> (name, values):prefillForm' nodes
            (value:_) -> (name, [value]):prefillForm' nodes
  where attrs' = M.mapKeys nameLocalName attrs
prefillForm' (NodeElement (Element (Name "textarea" _ _) attrs childs):nodes)
    | Just name <- "name" `M.lookup` attrs =
        (name, [Txt.concat [value | NodeContent value <- childs]]):prefillForm' nodes
prefillForm' (_:nodes) = prefillForm' nodes
prefillForm' [] = []

modifyForm :: ([Txt.Text] -> [Txt.Text]) -> Txt.Text -> Cursor -> Session -> IO ()
modifyForm cb name el session = modifyMVar_ session $ \session' -> do
    let form = findForm el $ id2els session'
    let blanked = prefillForm form $ fromDocument $ document session'
    let old = M.findWithDefault blanked form $ forms session'
    let new = HM.insert name (cb $ HM.lookupDefault [] name old) old
    return $ case form of
        Element (Name "form" _ _) _ _ ->
            session' { forms = M.insert form new $ forms session' }
        _ -> session'

serializeForm :: HM.HashMap Txt.Text [Txt.Text] -> Txt.Text
serializeForm = Txt.intercalate "&" . map joinEntry . expandValues . HM.toList
    where
        expandValues ((a, (b:bs)):rest) = (a, b):expandValues ((a, bs):rest)
        expandValues ((a, []):rest) = expandValues rest
        expandValues [] = []
        joinEntry (key, value) = key `Txt.append` Txt.cons '=' value

formAction :: Cursor -> Session -> IO (URI, Txt.Text, Txt.Text)
formAction el session = withMVar session $ \session' -> do
    let form = findForm el $ id2els session'
    let blanked = prefillForm form $ fromDocument $ document session'
    let inputs = M.findWithDefault blanked form $ forms session'
    let query = serializeForm inputs

    let fAttrs' = M.mapKeys nameLocalName $ elementAttributes form
    let iAttrs' = case node el of {
        NodeElement (Element _ attrs _) -> M.mapKeys nameLocalName attrs;
        _ -> M.empty
    }
    let action = case ("formaction" `M.lookup` iAttrs', "action" `M.lookup` fAttrs') of {
        (Just action, _) -> parseURIReference $ Txt.unpack action;
        (_, Just action) -> parseURIReference $ Txt.unpack action;
        (_, _) -> Nothing
    }
    let method = case ("formmethod" `M.lookup` iAttrs', "action" `M.lookup` fAttrs') of {
        (Just method, _) -> method;
        (_, Just method) -> method;
        (_, _) -> "GET"
    }
    return (fromMaybe nullURI action `relativeTo` currentURL session', query, method)