{-# LANGUAGE OverloadedStrings #-} module Data.HTML2CSS( externalStyles, externalStylesForURL, internalStyles, internalStylesForURL, cssPriorityAgent, cssPriorityUser, cssPriorityAuthor, traverseStyles, traversePrepopulatedStyles, traverseStyles', elToStylish ) 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.Syntax.Tokens (tokenize) import Network.URI ---- Constants cssPriorityAgent, cssPriorityUser, cssPriorityAuthor :: StyleSheet s => s -> s cssPriorityAgent = setPriority 1 cssPriorityUser = setPriority 2 cssPriorityAuthor = setPriority 3 ---- Parsing externalStyles :: StyleSheet s => s -> (M.Map XML.Name Txt.Text -> Bool) -> XML.Element -> (URI -> IO Txt.Text) -> IO s externalStyles a b c d = externalStylesForURL a b c nullURI d externalStylesForURL stylesheet testMedia html base loadURL = do css <- externalStyles' testMedia html base loadURL return $ foldl (\a (b, c) -> parseForURL a b c) (cssPriorityAuthor stylesheet) css externalStyles' testMedia html base loadURL = go $ linkedStyles' testMedia html where -- TODO parallelise loads go (link:links) = do response <- loadURL $ link rest <- go links return $ (relativeTo link base, response) : rest go [] = return [] linkedStyles' testMedia (XML.Element (XML.Name "link" _ _) attrs _) | Just link <- "href" `M.lookup` attrs, Just "stylesheet" <- "rel" `M.lookup` attrs, testMedia attrs, Just uri <- parseURIReference $ Txt.unpack link = [uri] linkedStyles' testMedia (XML.Element _ _ children) = concat [linkedStyles' testMedia el | XML.NodeElement el <- children] internalStyles a b c = internalStylesForURL a b nullURI c internalStylesForURL testMedia stylesheet base html = foldl (\s -> parseForURL s base) (cssPriorityAuthor stylesheet) $ internalStyles' testMedia html internalStyles' testMedia (XML.Element (XML.Name "style"_ _) attrs children) | testMedia attrs = [strContent children] internalStyles' testMedia (XML.Element _ _ children) = concat [internalStyles' testMedia el | XML.NodeElement el <- children] 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 traverseStyles :: PropertyParser s => (s -> [o] -> o) -> (s -> Txt.Text -> o) -> QueryableStyleSheet s -> XML.Element -> o traverseStyles = traverseStyles' Nothing temp Nothing (\x y -> Nothing) 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 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 = [] elToStylish (XML.Element (XML.Name name _ _) attrs _) parent previous = ElementNode { name = name, attributes = L.sort [ Attribute (XML.nameLocalName name) (Txt.unpack value) | (name, value) <- M.toList attrs ], parent = parent, previous = previous } addPsuedoclasses el psuedoclasses | (Attribute "" value : attrs) <- attributes el = el { attributes = Attribute "" (psuedoclasses ++ value) : attrs } | otherwise = el { attributes = Attribute "" psuedoclasses : attributes el }