~alcinnz/haskell-stylist

ref: 5c97dd0e1094c7f2e405f30a14133000be310cd5 haskell-stylist/xml-conduit-stylist/src/Data/HTML2CSS.hs -rw-r--r-- 5.1 KiB
5c97dd0e — Adrian Cochrane Fix build failure against new enough versions of 'text'. 2 years ago
                                                                                
3630f677 Adrian Cochrane
186cbffa Adrian Cochrane
03ba02a4 Adrian Cochrane
41adcf9d Adrian Cochrane
d7ce7863 Adrian Cochrane
03ba02a4 Adrian Cochrane
3630f677 Adrian Cochrane
c134671e Adrian Cochrane
3630f677 Adrian Cochrane
7ff2b69d 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
e45e40ba Adrian Cochrane
7ff2b69d Adrian Cochrane
e45e40ba Adrian Cochrane
7ff2b69d Adrian Cochrane
e45e40ba Adrian Cochrane
7ff2b69d Adrian Cochrane
28a70955 Adrian Cochrane
7ff2b69d Adrian Cochrane
2ba0a92a Adrian Cochrane
7ff2b69d Adrian Cochrane
5fa9521a Adrian Cochrane
d7ce7863 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
{-# LANGUAGE OverloadedStrings #-}
-- | Bindings from `xml-conduit` to `haskell-stylist`.
module Data.HTML2CSS(
        html2css, cssPriorityAgent, cssPriorityUser, cssPriorityAuthor, -- parsing
        preorder, el2styletree, els2stylist, el2stylist, stylize, stylize', stylizeEl, -- application
        inlinePseudos, stylizeNoPseudos, stylizeElNoPseudos
    ) 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

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

el2styletree el = StyleTree (Left el) $ mapMaybe node2styletree $ XML.elementNodes el
node2styletree (XML.NodeElement el) = Just $ el2styletree el
node2styletree (XML.NodeContent txt) = Just $ StyleTree (Right [("content", [String txt])]) []
node2styletree _ = Nothing

previous' (Just ElementNode {name = "", previous = prev'}) = previous' prev'
previous' prev' = prev'

els2stylist = preorder els2stylist'
els2stylist' parent previous (Left (XML.Element (XML.Name name ns _) attrs _)) =
    ElementNode {
        name = name, namespace = fromMaybe "" ns,
        attributes = L.sort [
            Attribute n (fromMaybe "" ns) $ Txt.unpack v | (XML.Name n ns _, v) <- M.toList attrs
        ],
        parent = parent, previous = previous' previous
    }
els2stylist' parent previous (Right attrs) = ElementNode {
        name = "", namespace = "",
        attributes = [Attribute "style" "" $ Txt.unpack $ Txt.concat style],
        parent = parent, previous = previous' previous
    } where style = concat [[prop, ": ", serialize v, "; "] | (prop, v) <- attrs]

el2stylist = els2stylist . el2styletree

stylize :: PropertyParser s => QueryableStyleSheet s -> StyleTree Element -> StyleTree [(Txt.Text, s)]
stylize = preorder . stylize'
stylize' :: PropertyParser s => QueryableStyleSheet s -> Maybe [(Txt.Text, s)] -> Maybe [(Txt.Text, s)] ->
        Element -> [(Txt.Text, s)]
stylize' stylesheet parent _ el = ("", base) : [
        (k, cascade' v [] base) | (k, v) <- HM.toList $ queryRules stylesheet el
    ] where
        base = cascade stylesheet el overrides $ fromMaybe temp $ lookup "" =<< parent
        overrides = concat [fst $ parseProperties' $ tokenize $ Txt.pack val
            | Attribute "style" _ val <- attributes el]
stylizeEl stylesheet = stylize stylesheet . el2stylist

inlinePseudos :: PropertyParser s => StyleTree [(Txt.Text, VarParser s)] -> StyleTree s
inlinePseudos (StyleTree self childs) = StyleTree {
        style = fromMaybe temp $ innerParser <$> lookup "" self,
        children = pseudo "before" ++ map inlinePseudos childs ++ pseudo "after"
    } where
        pseudo n
            | Just style <- innerParser <$> lookup n self,
                Just style' <- longhand style style "::" [Ident n] = [StyleTree style' []]
            | Just style <- innerParser <$> lookup n self = [StyleTree style []]
            | otherwise = []

stylizeNoPseudos css = inlinePseudos . stylize css
stylizeElNoPseudos css = inlinePseudos . stylizeEl css