{-# LANGUAGE OverloadedStrings #-}
-- | Bindings from `xml-conduit` to `haskell-stylist`.
module Data.HTML2CSS(
html2css, cssPriorityAgent, cssPriorityUser, cssPriorityAuthor, -- parsing
preorder, el2styletree, els2stylist, el2stylist, stylize, stylize' -- 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
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
preorder :: (Maybe b -> Maybe b -> a -> b) -> StyleTree a -> StyleTree b
preorder cb self = head $ preorder' cb Nothing Nothing [self]
preorder' :: (Maybe b -> Maybe b -> a -> b) ->
Maybe b -> Maybe b -> [StyleTree a] -> [StyleTree b]
preorder' cb parent previous (self:sibs) = let self' = cb parent previous $ style self
in StyleTree self' (preorder' cb (Just self') Nothing $ children self) :
preorder' cb parent (Just self') sibs
preorder' _ _ _ [] = []
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 = [
(k, if Txt.null k then base else 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]