{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Zenacy.HTML.Internal.Zip
( HTMLZipper
, HTMLZipAction
, HTMLIter
, HTMLZipPath(..)
, htmlZip
, htmlZipM
, htmlUnzip
, htmlUnzipM
, htmlZipNode
, htmlZipNodeM
, htmlZipRoot
, htmlZipRootM
, htmlZipUp
, htmlZipParent
, htmlZipFirst
, htmlZipLast
, htmlZipFind
, htmlZipNext
, htmlZipPrev
, htmlZipGet
, htmlZipTest
, htmlZipTestNode
, htmlZipTestName
, htmlZipTestFirst
, htmlZipTestLast
, htmlZipModify
, htmlZipModifyM
, htmlZipDelete
, htmlZipCollapse
, htmlZipInsertBefore
, htmlZipInsertAfter
, htmlZipContentBefore
, htmlZipContentAfter
, htmlZipContentLeft
, htmlZipContentRight
, htmlZipDropBefore
, htmlZipDropAfter
, htmlZipDropLeft
, htmlZipDropRight
, htmlZipPruneBefore
, htmlZipPruneAfter
, htmlZipPruneLeft
, htmlZipPruneRight
, htmlZipRepeat
, htmlZipStepNext
, htmlZipStepBack
, htmlZipSearch
, htmlZipIndex
, htmlIter
, htmlIterZipper
, htmlIterSearch
, htmlIterModify
, htmlIterNext
, htmlIterBack
, htmlZipPath
, htmlZipPathEmpty
, htmlZipPathFind
) where
import Zenacy.HTML.Internal.Core
import Zenacy.HTML.Internal.HTML
import Zenacy.HTML.Internal.Oper
import Control.Monad
( (>=>)
)
import Data.Bool
( bool
)
import Data.Default
( Default(..)
)
import Data.Maybe
( fromMaybe
, isNothing
)
import Data.Monoid
( (<>)
)
import Data.Text
( Text
)
import qualified Data.Text as T
( unpack
)
data HTMLCrumb = HTMLCrumb HTMLNode [HTMLNode] [HTMLNode]
data HTMLZipper = HTMLZipper HTMLNode [HTMLCrumb]
type HTMLZipAction = HTMLZipper -> Maybe HTMLZipper
data Direction = Down | Across | Up
data HTMLIter = HTMLIter Direction HTMLZipper
newtype HTMLZipPath = HTMLZipPath [Int] deriving (Show, Eq, Ord)
instance Default HTMLZipPath where
def = htmlZipPathEmpty
htmlZip :: HTMLNode -> HTMLZipper
htmlZip x = HTMLZipper x []
htmlZipM :: Monad m => HTMLNode -> m HTMLZipper
htmlZipM = pure . htmlZip
htmlUnzip :: HTMLZipper -> HTMLNode
htmlUnzip = htmlZipNode . htmlZipRoot
htmlUnzipM :: Monad m => HTMLZipper -> m HTMLNode
htmlUnzipM = pure . htmlUnzip
htmlZipNode :: HTMLZipper -> HTMLNode
htmlZipNode (HTMLZipper x _) = x
htmlZipNodeM :: Monad m => HTMLZipper -> m HTMLNode
htmlZipNodeM = pure . htmlZipNode
htmlZipRoot :: HTMLZipper -> HTMLZipper
htmlZipRoot x = maybe x htmlZipRoot $ htmlZipParent x
htmlZipRootM :: Monad m => HTMLZipper -> m HTMLZipper
htmlZipRootM = pure . htmlZipRoot
htmlZipUp :: HTMLZipper -> Maybe HTMLZipper
htmlZipUp = htmlZipParent
htmlZipParent :: HTMLZipper -> Maybe HTMLZipper
htmlZipParent = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n ls rs):cs) ->
let c = reverse ls <> [x] <> rs
in case n of
HTMLDocument n [] ->
Just $ HTMLZipper (HTMLDocument n c) cs
HTMLDoctype n p s ->
Nothing
HTMLFragment n [] ->
Just $ HTMLZipper (HTMLFragment n c) cs
HTMLElement n s a [] ->
Just $ HTMLZipper (HTMLElement n s a c) cs
HTMLTemplate s a c ->
Nothing
HTMLText t ->
Nothing
HTMLComment c ->
Nothing
htmlZipFirst :: HTMLZipper -> Maybe HTMLZipper
htmlZipFirst (HTMLZipper y z) = case y of
HTMLDocument n c ->
f c $ HTMLDocument n []
HTMLFragment n c ->
f c $ HTMLFragment n []
HTMLElement n s a c ->
f c $ HTMLElement n s a []
_ -> Nothing
where
f [] n = Nothing
f (h:rs) n = Just $ HTMLZipper h ((HTMLCrumb n [] rs):z)
htmlZipLast :: HTMLZipper -> Maybe HTMLZipper
htmlZipLast (HTMLZipper y z) = case y of
HTMLDocument n c ->
f c $ HTMLDocument n []
HTMLFragment n c ->
f c $ HTMLFragment n []
HTMLElement n s a c ->
f c $ HTMLElement n s a []
_ -> Nothing
where
f [] n = Nothing
f xs n = let (h:ls) = reverse xs in Just $
HTMLZipper h ((HTMLCrumb n ls []):z)
htmlZipFind :: (HTMLNode -> Bool) -> HTMLZipper -> Maybe HTMLZipper
htmlZipFind p (HTMLZipper y z) = case y of
HTMLDocument n c ->
f c $ HTMLDocument n []
HTMLFragment n c ->
f c $ HTMLFragment n []
HTMLElement n s a c ->
f c $ HTMLElement n s a []
_ -> Nothing
where
f c n = case break p c of
(ls, []) -> Nothing
(ls, h:rs) -> Just $
HTMLZipper h ((HTMLCrumb n (reverse ls) rs):z)
htmlZipNext :: HTMLZipper -> Maybe HTMLZipper
htmlZipNext = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n ls []):cs) -> Nothing
HTMLZipper x ((HTMLCrumb n ls (h:rs)):cs) ->
Just $ HTMLZipper h ((HTMLCrumb n (x:ls) rs):cs)
htmlZipPrev :: HTMLZipper -> Maybe HTMLZipper
htmlZipPrev = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n [] rs):cs) -> Nothing
HTMLZipper x ((HTMLCrumb n (h:ls) rs):cs) ->
Just $ HTMLZipper h ((HTMLCrumb n ls (x:rs)):cs)
htmlZipGet :: Int -> HTMLZipper -> Maybe HTMLZipper
htmlZipGet n z
| n < 0 = Nothing
| n == 0 = htmlZipFirst z
| otherwise = htmlZipFirst z >>= f n
where
f 0 = Just
f n = htmlZipNext >=> f (n - 1)
htmlZipTest :: (HTMLZipper -> Bool) -> HTMLZipper -> Maybe HTMLZipper
htmlZipTest f z = bool Nothing (Just z) $ f z
htmlZipTestNode :: (HTMLNode -> Bool) -> HTMLZipper -> Maybe HTMLZipper
htmlZipTestNode f = htmlZipTest $ f . htmlZipNode
htmlZipTestName :: Text -> HTMLZipper -> Maybe HTMLZipper
htmlZipTestName x = htmlZipTest (htmlElemHasName x . htmlZipNode)
htmlZipTestFirst :: HTMLZipper -> Maybe HTMLZipper
htmlZipTestFirst = htmlZipTest (isNothing . htmlZipPrev)
htmlZipTestLast :: HTMLZipper -> Maybe HTMLZipper
htmlZipTestLast = htmlZipTest (isNothing . htmlZipNext)
htmlZipModify :: (HTMLNode -> HTMLNode) -> HTMLZipper -> HTMLZipper
htmlZipModify f (HTMLZipper y z) = HTMLZipper (f y) z
htmlZipModifyM :: Monad m => (HTMLNode -> HTMLNode) -> HTMLZipper -> m HTMLZipper
htmlZipModifyM f = pure . htmlZipModify f
htmlZipDelete :: HTMLZipper -> Maybe HTMLZipper
htmlZipDelete = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n l r):cs) ->
let c = reverse l <> r
in case n of
HTMLDocument n [] ->
Just $ HTMLZipper (HTMLDocument n c) cs
HTMLFragment n [] ->
Just $ HTMLZipper (HTMLFragment n c) cs
HTMLElement n s a [] ->
Just $ HTMLZipper (HTMLElement n s a c) cs
_ -> Nothing
htmlZipCollapse :: HTMLZipper -> Maybe HTMLZipper
htmlZipCollapse = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n l r):cs) ->
let c = reverse l <> htmlNodeContent x <> r
in case n of
HTMLDocument n [] ->
Just $ HTMLZipper (HTMLDocument n c) cs
HTMLFragment n [] ->
Just $ HTMLZipper (HTMLFragment n c) cs
HTMLElement n s a [] ->
Just $ HTMLZipper (HTMLElement n s a c) cs
_ -> Nothing
htmlZipInsertBefore :: HTMLNode -> HTMLZipper -> Maybe HTMLZipper
htmlZipInsertBefore y = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n l r):cs) ->
Just $ HTMLZipper x ((HTMLCrumb n (y:l) r):cs)
htmlZipInsertAfter :: HTMLNode -> HTMLZipper -> Maybe HTMLZipper
htmlZipInsertAfter y = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n l r):cs) ->
Just $ HTMLZipper x ((HTMLCrumb n l (y:r)):cs)
htmlZipContentBefore :: HTMLZipper -> [HTMLNode]
htmlZipContentBefore = \case
HTMLZipper x [] -> []
HTMLZipper x ((HTMLCrumb n l r):cs) -> reverse l
htmlZipContentAfter :: HTMLZipper -> [HTMLNode]
htmlZipContentAfter = \case
HTMLZipper x [] -> []
HTMLZipper x ((HTMLCrumb n l r):cs) -> r
htmlZipContentLeft :: HTMLZipper -> [HTMLNode]
htmlZipContentLeft = htmlZipContentBefore
htmlZipContentRight :: HTMLZipper -> [HTMLNode]
htmlZipContentRight = htmlZipContentAfter
htmlZipDropBefore :: HTMLZipper -> Maybe HTMLZipper
htmlZipDropBefore = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n _ r):cs) ->
Just $ HTMLZipper x ((HTMLCrumb n [] r):cs)
htmlZipDropAfter :: HTMLZipper -> Maybe HTMLZipper
htmlZipDropAfter = \case
HTMLZipper x [] -> Nothing
HTMLZipper x ((HTMLCrumb n l _):cs) ->
Just $ HTMLZipper x ((HTMLCrumb n l []):cs)
htmlZipDropLeft :: HTMLZipper -> Maybe HTMLZipper
htmlZipDropLeft = htmlZipDropBefore
htmlZipDropRight :: HTMLZipper -> Maybe HTMLZipper
htmlZipDropRight = htmlZipDropAfter
htmlZipPruneBefore :: HTMLZipper -> Maybe HTMLZipper
htmlZipPruneBefore = htmlZipRepeat safeDrop htmlZipParent
where
safeDrop z = Just $ fromMaybe z $ htmlZipDropBefore z
htmlZipPruneAfter :: HTMLZipper -> Maybe HTMLZipper
htmlZipPruneAfter = htmlZipRepeat safeDrop htmlZipParent
where
safeDrop z = Just $ fromMaybe z $ htmlZipDropAfter z
htmlZipPruneLeft :: HTMLZipper -> Maybe HTMLZipper
htmlZipPruneLeft = htmlZipPruneBefore
htmlZipPruneRight :: HTMLZipper -> Maybe HTMLZipper
htmlZipPruneRight = htmlZipPruneAfter
htmlZipRepeat :: HTMLZipAction -> HTMLZipAction -> HTMLZipAction
htmlZipRepeat f g z =
case f z of
Nothing -> Nothing
Just z1 -> case g z1 of
Nothing -> Just z1
Just z2 -> htmlZipRepeat f g z2
htmlZipStepNext :: HTMLZipper -> Maybe HTMLZipper
htmlZipStepNext = htmlZipStep htmlZipFirst htmlZipNext
htmlZipStepBack :: HTMLZipper -> Maybe HTMLZipper
htmlZipStepBack = htmlZipStep htmlZipLast htmlZipPrev
htmlZipStep :: HTMLZipAction -> HTMLZipAction -> HTMLZipAction
htmlZipStep first next z =
case first z of
Just x -> Just x
Nothing -> f z
where
f x = case next x of
Just a -> Just a
Nothing -> case htmlZipParent x of
Just b -> f b
Nothing -> Nothing
htmlZipSearch
:: (HTMLZipper -> Maybe HTMLZipper)
-> (HTMLZipper -> Bool)
-> HTMLZipper
-> Maybe HTMLZipper
htmlZipSearch step test x
| test x = Just x
| otherwise = maybe Nothing (htmlZipSearch step test) $ step x
htmlZipIndex :: HTMLZipper -> Maybe Int
htmlZipIndex = \case
HTMLZipper _ [] -> Nothing
HTMLZipper _ ((HTMLCrumb _ ls _):_) -> Just $ length ls
htmlZipDump :: HTMLZipper -> String
htmlZipDump (HTMLZipper n cs) =
name n <> "\n" <> go cs
where
go :: [HTMLCrumb] -> String
go [] = ""
go ((HTMLCrumb n ls rs):cs) =
name n <> "\n"
<> " ls: " <> names ls <> "\n"
<> " rs: " <> names rs <> "\n"
<> go cs
name = T.unpack . htmlElemName
names = show . map name
htmlIter :: HTMLZipper -> HTMLIter
htmlIter = HTMLIter Down
htmlIterZipper :: HTMLIter -> HTMLZipper
htmlIterZipper (HTMLIter _ z) = z
htmlIterModify :: (HTMLZipper -> HTMLZipper) -> HTMLIter -> HTMLIter
htmlIterModify f (HTMLIter d z) = (HTMLIter d $ f z)
htmlIterNext :: HTMLIter -> Maybe HTMLIter
htmlIterNext = iterStep htmlZipFirst htmlZipNext
htmlIterBack :: HTMLIter -> Maybe HTMLIter
htmlIterBack = iterStep htmlZipLast htmlZipPrev
iterStep
:: (HTMLZipper -> Maybe HTMLZipper)
-> (HTMLZipper -> Maybe HTMLZipper)
-> HTMLIter
-> Maybe HTMLIter
iterStep first next = go
where
go (HTMLIter d z) =
case d of
Down ->
case first z of
Just x -> Just $ HTMLIter Down x
Nothing -> go $ HTMLIter Across z
Across ->
case next z of
Just x -> Just $ HTMLIter Down x
Nothing -> go $ HTMLIter Up z
Up ->
case htmlZipParent z of
Just x -> Just $ HTMLIter Across x
Nothing -> Nothing
htmlIterSearch
:: (HTMLIter -> Maybe HTMLIter)
-> (HTMLZipper -> Bool)
-> HTMLIter
-> Maybe HTMLIter
htmlIterSearch step test x@(HTMLIter _ z)
| test z = Just x
| otherwise = maybe Nothing (htmlIterSearch step test) $ step x
htmlZipPathEmpty :: HTMLZipPath
htmlZipPathEmpty = HTMLZipPath []
htmlZipPath :: HTMLZipper -> HTMLZipPath
htmlZipPath = maybe (HTMLZipPath []) id . go htmlZipPathEmpty
where
go :: HTMLZipPath -> HTMLZipper -> Maybe HTMLZipPath
go (HTMLZipPath p) z =
case htmlZipIndex z of
Nothing ->
Just $ HTMLZipPath p
Just x ->
case htmlZipParent z of
Nothing -> Nothing
Just y -> go (HTMLZipPath $ x : p) y
htmlZipPathFind :: HTMLZipPath -> HTMLZipper -> Maybe HTMLZipper
htmlZipPathFind (HTMLZipPath p) = f p
where
f [] = pure
f (x:xs) = htmlZipGet x >=> f xs