~alcinnz/haskell-stylist

ref: e45e40ba46103ccef5b5d3fe040e95376c2291e7 haskell-stylist/xml-conduit-stylist/src/Data/HTML2CSS.hs -rw-r--r-- 6.5 KiB
e45e40ba — Adrian Cochrane XML Conduit Stylist Refactor: convert XML Conduit tree to Haskell Stylist Element StyleTree. 4 years ago
                                                                                
3630f677 Adrian Cochrane
186cbffa Adrian Cochrane
03ba02a4 Adrian Cochrane
41adcf9d Adrian Cochrane
03ba02a4 Adrian Cochrane
3630f677 Adrian Cochrane
c134671e Adrian Cochrane
3630f677 Adrian Cochrane
d073900e Adrian Cochrane
3630f677 Adrian Cochrane
e45e40ba Adrian Cochrane
41adcf9d Adrian Cochrane
3630f677 Adrian Cochrane
93288bed Adrian Cochrane
d073900e Adrian Cochrane
3630f677 Adrian Cochrane
93288bed Adrian Cochrane
41adcf9d Adrian Cochrane
7d1e8859 Adrian Cochrane
41adcf9d Adrian Cochrane
93288bed Adrian Cochrane
41adcf9d Adrian Cochrane
3630f677 Adrian Cochrane
186cbffa Adrian Cochrane
932c3586 Adrian Cochrane
d073900e Adrian Cochrane
186cbffa Adrian Cochrane
d073900e Adrian Cochrane
186cbffa Adrian Cochrane
ce996690 Adrian Cochrane
d073900e Adrian Cochrane
ce996690 Adrian Cochrane
d073900e Adrian Cochrane
7061161d Adrian Cochrane
ce996690 Adrian Cochrane
3630f677 Adrian Cochrane
c134671e Adrian Cochrane
7061161d Adrian Cochrane
3630f677 Adrian Cochrane
c134671e Adrian Cochrane
d073900e Adrian Cochrane
c134671e Adrian Cochrane
ce996690 Adrian Cochrane
d073900e Adrian Cochrane
ce996690 Adrian Cochrane
c134671e Adrian Cochrane
3630f677 Adrian Cochrane
186cbffa Adrian Cochrane
277f756c Adrian Cochrane
3630f677 Adrian Cochrane
277f756c Adrian Cochrane
3630f677 Adrian Cochrane
277f756c Adrian Cochrane
3630f677 Adrian Cochrane
e45e40ba 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{-# LANGUAGE OverloadedStrings #-}
-- | Bindings from `xml-conduit` to `haskell-stylist`.
module Data.HTML2CSS(
        html2css, cssPriorityAgent, cssPriorityUser, cssPriorityAuthor, -- parsing
        traverseStyles, traversePrepopulatedStyles, traverseStyles', elToStylish -- application
    ) where

import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as Txt
import Data.Maybe (fromMaybe)

import qualified Text.XML as XML
import Data.CSS.Syntax.StyleSheet
import Data.CSS.Style
import Data.CSS.StyleTree
import Data.CSS.Syntax.Tokens
import Data.CSS.Preprocessor.Conditions
import qualified Data.CSS.Preprocessor.Conditions.Expr as Query
import Network.URI

---- Constants
-- | Set the priority for a CSS stylesheet being parsed.
cssPriorityAgent, cssPriorityUser, cssPriorityAuthor :: StyleSheet s => s -> s
cssPriorityAgent = setPriority 1
cssPriorityUser = setPriority 2
cssPriorityAuthor = setPriority 3

---- Parsing
-- | Converts a parsed XML or HTML file to a `ConditionalStyles` `StyleSheet`.
html2css :: PropertyParser p => XML.Document -> URI -> ConditionalStyles p
html2css xml url = testIsStyled $ ConditionalStyles {
    hostURL = url,
    mediaDocument = "document",
    isUnstyled = False,
    rules = Priority 3 : html2css' (XML.documentRoot xml) (conditionalStyles url "document"),
    propertyParser = temp
}

html2css' :: PropertyParser p => XML.Element -> ConditionalStyles p -> [ConditionalRule p]
html2css' (XML.Element (XML.Name "style" _ _) attrs children) base =
    [Internal (parseMediaQuery attrs) (parseForURL base (hostURL base) $ strContent children)]
html2css' (XML.Element (XML.Name "link" _ _) attrs _) base
    | Just link <- "href" `M.lookup` attrs,
        Just "stylesheet" <- "rel" `M.lookup` attrs,
        Just uri <- parseURIReference $ Txt.unpack link =
            [External (parseMediaQuery attrs) (relativeTo uri $ hostURL base)]
html2css' (XML.Element _ _ children) base = concat [html2css' el base | XML.NodeElement el <- children]

parseMediaQuery :: M.Map XML.Name Txt.Text -> Query.Expr
parseMediaQuery attrs
    | Just text <- "media" `M.lookup` attrs = Query.parse' (tokenize text) []
    | otherwise = []


strContent :: [XML.Node] -> Txt.Text
strContent (XML.NodeContent text : rest) = text `Txt.append` strContent rest
-- We do want to read in comments for CSS, just not for display.
strContent (XML.NodeComment text : rest) = text `Txt.append` strContent rest
strContent (XML.NodeElement (XML.Element _ _ children):rest) =
    strContent children `Txt.append` strContent rest
strContent (_:rest) = strContent rest
strContent [] = ""

---- Styling
-- | Converts a parsed XML or HTML document to a specified style tree type.
traverseStyles :: PropertyParser s => (s -> [o] -> o) -> (s -> Txt.Text -> o) ->
        QueryableStyleSheet s -> XML.Element -> o
traverseStyles = traverseStyles' Nothing temp Nothing (\x y -> Nothing)
-- | Converts a parsed XML or HTML document to a specified style tree type,
-- with a routine to compute alternative contents based on the raw element or computed styles.
traversePrepopulatedStyles :: PropertyParser s => (s -> XML.Element -> Maybe [o]) ->
        (s -> [o] -> o) -> (s -> Txt.Text -> o) -> QueryableStyleSheet s -> XML.Element -> o
traversePrepopulatedStyles = traverseStyles' Nothing temp Nothing
-- | Full routine for converting a parsed XML or HTML document to a specified style tree type.
traverseStyles' :: PropertyParser s => Maybe Element -> s -> Maybe Element ->
        (s -> XML.Element -> Maybe [o]) -> (s -> [o] -> o) -> (s -> Txt.Text -> o) ->
        QueryableStyleSheet s -> XML.Element -> o
traverseStyles' parent parentStyle previous prepopulate builder textBuilder stylesheet el@(
        XML.Element _ attrs children
    ) = builder style traverseChildren
    where
        stylishEl = elToStylish el parent previous
        maybeEl = Just stylishEl
        rules = queryRules stylesheet stylishEl
        style = cascade' (HM.lookupDefault [] "" rules) overrides parentStyle
        overrides | Just styleAttr <- "style" `M.lookup` attrs =
                fst $ parseProperties' $ tokenize styleAttr
            | otherwise = []

        traverseChildren = traversePsuedo' "before" ++
                fromMaybe (traverseChildren' Nothing children) (prepopulate style el) ++
                traversePsuedo' "after"
        traversePsuedo' psuedo = traversePsuedo rules psuedo style builder
        traverseChildren' prev (XML.NodeContent txt:nodes) =
            textBuilder style txt : traverseChildren' prev nodes
        traverseChildren' prev (XML.NodeElement el:nodes) =
            traverseStyles' maybeEl style prev prepopulate builder textBuilder stylesheet el :
                traverseChildren' (Just $ elToStylish el maybeEl prev) nodes
        traverseChildren' prev (_:nodes) = traverseChildren' prev nodes
        traverseChildren' _ [] = []
traversePsuedo rules psuedo parentStyle builder
    | Just rules' <- HM.lookup psuedo rules = [builder (cascade' rules' [] parentStyle) []]
    | otherwise = []

-- | Converts a xml-conduit Element to a stylist Element.
elToStylish (XML.Element (XML.Name name ns _) attrs _) parent previous =
    ElementNode {
        name = name,
        namespace = fromMaybe "" ns,
        attributes = L.sort [
            Attribute name (fromMaybe "" ns) (Txt.unpack value)
            | (XML.Name name ns _, value) <- M.toList attrs
        ],
        parent = parent,
        previous = previous
    }

el2stylist el = nodes2stylist [XML.NodeElement el] Nothing Nothing
nodes2stylist (XML.NodeElement (XML.Element (XML.Name name ns _) attrs childs):nodes) parent previous =
    let el = ElementNode {
        name = name,
        namespace = fromMaybe "" ns,
        attributes = L.sort [
            Attribute name (fromMaybe "" ns) (Txt.unpack value)
            | (XML.Name name ns _, value) <- M.toList attrs
        ],
        parent = parent,
        previous = previous
    } in StyleTree {
        style = el,
        children = nodes2stylist childs (Just el) Nothing
    } : nodes2stylist nodes parent previous
nodes2stylist (XML.NodeContent txt:nodes) parent previous = StyleTree {
        style = ElementNode {
            name = "", namespace = "",
            attributes = [Attribute "style" "" $ Txt.unpack $
                Txt.append "content: " $ serialize [String txt]],
            parent = parent, previous = previous
        },
        children = []
    } : nodes2stylist nodes parent previous
nodes2stylist (_:nodes) parent previous = nodes2stylist nodes parent previous
nodes2stylist [] _ _ = []