{-# LANGUAGE OverloadedStrings #-}
{- | This module exports the top level constructors
used for building node transformations.
For example, if your template is
>
>
>
>
and you invoke hquery like this:
> import Text.Hquery
> template = ... -- parse your template here
> people = [ ("Justin Bieber", "Celebrity")
> , ("Jens Kidman", "Musician")
> ]
> bindPerson (n, o) = hq ".name *" n . hq ".occupation *" o
> f = hq ".person *" $ map bindPerson people
> f template
you'll get markup like this:
>
>
Justin Bieber
>
Celebrity
>
>
>
Jens Kidman
>
Musician
>
You can also add, remove, and append to element attributes. For example if we
have: @ \
\
@, below are some example
transformations:
* @ hq \"div [class+]\" \"hidden\" @ gives @ \
\
@
* @ hq \".foo [id]\" \"bar\" @ gives @ \
\
@
* @ hq \"* [class!]\" \"foo\" @ gives @ \
\
@
This module exports several constructors for common types of node
transformations. These constructors simply give you back a @ 'Node' -> 'Node'
@, which you can then apply however you choose.
-}
module Text.Hquery (
-- * Constructors
MakeTransformer(..),
Group(..),
-- * Values
-- | nothing is handy for deleting a node from the tree, you cna replace it
-- with nothing, e.g. @ hq \".foo\" nothing @
nothing,
) where
import Data.List
import Data.Maybe
import qualified Data.Text as T
import Text.Parsec
import Text.XmlHtml
import Text.XmlHtml.Cursor
import Text.Hquery.Internal.Error
import Text.Hquery.Internal.Selector
import Text.Hquery.Internal.Transform
import Text.Hquery.Utils
parseSel :: String ->
(Maybe AttrSel -> Cursor -> Maybe Cursor) ->
[Node] ->
[Node]
parseSel sel builder = case parse commandParser "" sel of
Left _ -> id -- TODO: error handling? invalid sel
Right (css, attr) -> transform css (builder attr)
nothing :: [Node]
nothing = []
data Group = Group [Node]
class MakeTransformer a where
hq :: String -> a -> [Node] -> [Node]
instance MakeTransformer a => MakeTransformer (Maybe a) where
hq sel Nothing = hq sel nothing
hq sel (Just t) = hq sel t
instance MakeTransformer String where
hq sel target = parseSel sel nodeXform
where
packed = T.pack target
n = TextNode packed
nodeXform attr = Just . case attr of
Just (AttrSel t m) -> buildAttrMod t m packed
Just Append -> mapChildren (++ [n])
Just CData -> mapChildren (const [n])
Nothing -> setNode n
instance MakeTransformer [String] where
hq sel = hq sel . map (TextNode . T.pack)
instance MakeTransformer Node where
hq sel target = hq sel [target]
instance MakeTransformer Group where
hq sel (Group ns) = parseSel sel groupXform
where
groupXform attr c = case (attr, current c) of
(Just CData, e @ Element {}) -> Just $ setNode (e { elementChildren = ns }) c
(Just _, _) -> Just c -- TODO: error handling?
(Nothing, _) -> replaceCurrent ns c
instance MakeTransformer ([Node] -> [Node]) where
hq sel f = hq sel [f]
instance MakeTransformer [[Node] -> [Node]] where
hq sel fs = parseSel sel (\_ -> replicateAndApply)
where
replicateAndApply c = let n = (current c)
ns = concat $ fmap ($[n]) fs
in replaceCurrent ns c
instance MakeTransformer [Node] where
hq sel ns = parseSel sel buildNodesXform
where
buildNodesXform (Just CData) = replicateNode
buildNodesXform (Just Append) = Just . mapChildren (++ ns)
buildNodesXform (Just (AttrSel _ _)) = Just -- TODO: error handling? can't insert nodes in an attr
buildNodesXform Nothing = replaceCurrent ns
replicateNode :: Cursor -> Maybe Cursor
replicateNode c = let n = (current c) in
case n of
e @ Element {} ->
let replicated = map (\x -> e { elementChildren = [x] }) ns
in replaceCurrent replicated c
_ -> raise "bug: shouldn't be replicating on a non-Element node"
replaceCurrent :: [Node] -> Cursor -> Maybe Cursor
replaceCurrent ns c = fromMaybe dflt $ do
p <- parent c
case current p of
pn@Element { elementChildren = kids } -> do
ix <- elemIndex curN kids
let next = setNode (pn { elementChildren = concatMap replaceN kids }) p
let childIdx = (ix - 1 + (length ns))
return $ Just $ fromMaybe next $ getChild childIdx next
_ -> raise "should be no non-Element parents!"
where
curN = current c
replaceN n2 = if n2 == curN then ns else [n2]
dflt = do
newCur <- (fromNodes ns)
return (fromMaybe newCur $ findRight isLast newCur)