@@ -0,0 +1,70 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ViewPatterns #-}
3+
4+module Text.HTML.Tree
5+ ( -- * Constructing forests
6+ tokensToForest
7+ , ParseTokenForestError(..), PStack(..)
8+ , nonClosing
9+ -- * Deconstructing forests
10+ , tokensFromForest
11+ , tokensFromTree
12+ ) where
13+
14+import Data.Monoid
15+import Data.Text (Text)
16+import Data.Tree
17+import Text.HTML.Parser
18+
19+
20+tokensToForest :: [Token] -> Either ParseTokenForestError (Forest Token)
21+tokensToForest = f (PStack [] [])
22+ where
23+ f (PStack ss []) [] = Right (reverse ss)
24+ f pstack [] = Left $ ParseTokenForestErrorBracketMismatch pstack Nothing
25+ f pstack (t : ts) = case t of
26+ TagOpen n _ -> if n `elem` nonClosing
27+ then f (pushFlatSibling t pstack) ts
28+ else f (pushParent t pstack) ts
29+ TagClose n -> (`f` ts) =<< popParent n pstack
30+ ContentChar _ -> f (pushFlatSibling t pstack) ts
31+ ContentText _ -> f (pushFlatSibling t pstack) ts
32+ Comment _ -> f (pushFlatSibling t pstack) ts
33+ Doctype _ -> f (pushFlatSibling t pstack) ts
34+
35+nonClosing :: [Text]
36+nonClosing = ["br", "hr", "img"]
37+
38+data ParseTokenForestError =
39+ ParseTokenForestErrorBracketMismatch PStack (Maybe Token)
40+ deriving (Eq, Show)
41+
42+data PStack = PStack
43+ { _pstackToplevelSiblings :: Forest Token
44+ , _pstackParents :: [(Token, Forest Token)]
45+ }
46+ deriving (Eq, Show)
47+
48+pushParent :: Token -> PStack -> PStack
49+pushParent t (PStack ss ps) = PStack [] ((t, ss) : ps)
50+
51+popParent :: TagName -> PStack -> Either ParseTokenForestError PStack
52+popParent n (PStack ss ((p@(TagOpen n' _), ss') : ps))
53+ | n == n' = Right $ PStack (Node p (reverse ss) : ss') ps
54+popParent n pstack
55+ = Left $ ParseTokenForestErrorBracketMismatch pstack (Just $ TagClose n)
56+
57+pushFlatSibling :: Token -> PStack -> PStack
58+pushFlatSibling t (PStack ss ps) = PStack (Node t [] : ss) ps
59+
60+
61+tokensFromForest :: Forest Token -> [Token]
62+tokensFromForest = mconcat . fmap tokensFromTree
63+
64+tokensFromTree :: Tree Token -> [Token]
65+tokensFromTree (Node o@(TagOpen n _) ts) | n `notElem` nonClosing
66+ = [o] <> tokensFromForest ts <> [TagClose n]
67+tokensFromTree (Node t [])
68+ = [t]
69+tokensFromTree _
70+ = error "renderTokenTree: leaf node with children."