----------------------------------------------------------------------------- -- Copyright 2019, Ideas project team. This file is distributed under the -- terms of the Apache License 2.0. For more information, see the files -- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution. ----------------------------------------------------------------------------- -- | -- Maintainer : bastiaan.heeren@ou.nl -- Stability : provisional -- Portability : portable (depends on ghc) -- -- A minimal interface for constructing simple HTML pages -- See http://www.w3.org/TR/html4/ -- ----------------------------------------------------------------------------- module Ideas.Text.HTML ( ToHTML(..), HTMLPage, HTMLBuilder , addCSS, addScript, addStyle, showHTML , string, text , htmlPage, link , h1, h2, h3, h4, h5, h6 , preText, ul, table, keyValueTable , image, space, spaces, (<#>), spaced , highlightXML , para, ttText, hr, br, pre, bullet , divClass, spanClass -- HTML generic attributes , idA, classA, styleA, titleA -- Font style elements , tt, italic, bold, big, small ) where import Data.Char import Data.List import Data.Monoid import Ideas.Text.XML import Prelude hiding (div) import qualified Data.Map as M import qualified Ideas.Text.XML as XML type HTMLBuilder = XMLBuilder class ToHTML a where toHTML :: a -> HTMLBuilder listToHTML :: [a] -> HTMLBuilder -- default definitions listToHTML = ul . map toHTML instance ToHTML a => ToHTML [a] where toHTML = listToHTML instance (ToHTML a, ToHTML b) => ToHTML (Either a b) where toHTML = either toHTML toHTML instance (ToHTML a) => ToHTML (Maybe a) where toHTML = maybe mempty toHTML instance ToHTML () where toHTML _ = mempty instance (ToHTML a, ToHTML b) => ToHTML (a, b) where toHTML (a, b) = toHTML a <#> toHTML b instance (ToHTML a, ToHTML b, ToHTML c) => ToHTML (a, b, c) where toHTML (a, b, c) = toHTML a <#> toHTML b <#> toHTML c instance (ToHTML a, ToHTML b) => ToHTML (M.Map a b) where toHTML = Ideas.Text.HTML.table False . map f . M.toList where f (a, b) = [toHTML a, toHTML b] instance ToHTML Int where toHTML = text instance ToHTML Bool where toHTML = text instance ToHTML Char where toHTML = string . return listToHTML = string data HTMLPage = HTMLPage { title :: String , styleSheets :: [FilePath] , scripts :: [FilePath] , styleTxts :: [String] , htmlContent :: HTMLBuilder } instance ToXML HTMLPage where toXML page = makeXML "html" $ element "head" [ tag "title" (string (title page)) , mconcat [ element "link" [ "rel" .=. "STYLESHEET" , "href" .=. css , "type" .=. "text/css" ] | css <- styleSheets page ] , mconcat [ tag "style" (string txt) | txt <- styleTxts page ] , mconcat [ element "script" ["src" .=. js, "type" .=. "text/javascript", string " "] | js <- scripts page ] ] <> tag "body" (htmlContent page) showHTML :: HTMLPage -> String showHTML = compactXML . toXML addCSS :: FilePath -> HTMLPage -> HTMLPage addCSS css page = page { styleSheets = css : styleSheets page } addScript :: FilePath -> HTMLPage -> HTMLPage addScript js page = page { scripts = js : scripts page } addStyle :: String -> HTMLPage -> HTMLPage addStyle txt page = page { styleTxts = txt : styleTxts page } -- html helper functions htmlPage :: String -> HTMLBuilder -> HTMLPage htmlPage s = HTMLPage s [] [] [] link :: BuildXML a => String -> a -> a link url body = tag "a" $ ("href" .=. url) <> body h1, h2, h3, h4, h5, h6 :: BuildXML a => String -> a h1 = tag "h1" . string h2 = tag "h2" . string h3 = tag "h3" . string h4 = tag "h4" . string h5 = tag "h5" . string h6 = tag "h6" . string para :: BuildXML a => a -> a para = tag "p" preText :: BuildXML a => String -> a preText = pre . string pre :: BuildXML a => a -> a pre = tag "pre" hr :: BuildXML a => a hr = emptyTag "hr" br :: BuildXML a => a br = emptyTag "br" ttText :: BuildXML a => String -> a ttText = tt . string ul :: BuildXML a => [a] -> a ul xs | null xs = mempty | otherwise = element "ul" (map (tag "li") xs) -- | First argument indicates whether the table has a header or not table :: BuildXML a => Bool -> [[a]] -> a table b rows | null rows = mempty | otherwise = element "table" $ ("border" .=. "1") : [ element "tr" $ ("class" .=. getClass i) : [ tag "td" c | c <- row ] | (i, row) <- zip [0::Int ..] rows ] where getClass i | i == 0 && b = "top-row" | even i = "even-row" | otherwise = "odd-row" keyValueTable :: BuildXML a => [(String, a)] -> a keyValueTable = let f (s, a) = [spanClass "table-key" (string s), a] in para . table False . map f spaces :: BuildXML a => Int -> a spaces n = mconcat (replicate n space) space, bullet :: BuildXML a => a space = XML.string [chr 160] --   bullet = XML.string [chr 8226] (<#>) :: BuildXML a => a -> a -> a x <#> y = x <> space <> y spaced :: BuildXML a => [a] -> a spaced = mconcat . intersperse space image :: BuildXML a => String -> a image n = tag "img" ("src" .=. n) divClass :: BuildXML a => String -> a -> a divClass n a = tag "div" (classA n <> a) spanClass :: BuildXML a => String -> a -> a spanClass n a = tag "span" (classA n <> a) -- A simple XML highlighter highlightXML :: Bool -> XML -> HTMLBuilder highlightXML nice | nice = tag "pre" . f . prettyXML | otherwise = tag "tt" . f . compactXML where -- find < f :: String -> HTMLBuilder f [] = mempty f ('<':'/':xs) = g " f xs -- find > g start acc [] = string (start ++ reverse acc) g start acc ('/':'>':xs) = pp (start, reverse acc, "/>") <> f xs g start acc ('>':xs) = pp (start, reverse acc, ">") <> f xs g start acc (x:xs) = g start (x:acc) xs pp (start, info, end) = blue (string (start ++ as)) <> rec bs <> blue (string end) where (as, bs) = span isAlphaNum info rec [] = mempty rec ('=':xs) = orange (string "=") <> rec xs rec ('"':xs) = case break (== '"') xs of (xs1, _:xs2) -> green (string ('"' : xs1 ++ ['"'])) <> rec xs2 _ -> string ('"':xs) rec (x:xs) = string [x] <> rec xs blue a = tag "font" ("color" .=. "blue" <> a) orange a = tag "font" ("color" .=. "orange" <> a) green a = tag "font" ("color" .=. "green" <> a) {- f [] = [] f list@(x:xs) | "</" `isPrefixOf` list = -- close tag let (as, bs) = span isAlphaNum (drop 5 list) in "</" ++ as ++ "" ++ g bs | "<" `isPrefixOf` list = -- open tag let (as, bs) = span isAlphaNum (drop 4 list) in "<" ++ as ++ "" ++ g bs | otherwise = x : f xs -- find > g [] = [] g list@(x:xs) | "/>" `isPrefixOf` list = "/>" ++ f (drop 5 list) | ">" `isPrefixOf` list = ">" ++ f (drop 4 list) | x=='=' = "=" ++ g xs | otherwise = x : g xs -} ----------------------------------------------------------- -- * HTML generic attributes idA, classA, styleA, titleA :: BuildXML a => String -> a idA = ("id" .=.) -- document-wide unique id classA = ("class" .=.) -- space-separated list of classes styleA = ("style" .=.) -- associated style info titleA = ("title" .=.) -- advisory title ----------------------------------------------------------- -- * Font style elements -- | Renders as teletype or monospaced Ideas.Text. tt :: BuildXML a => a -> a tt = tag "tt" -- | Renders as italic text style. italic :: BuildXML a => a -> a italic = tag "i" -- | Renders as bold text style. bold :: BuildXML a => a -> a bold = tag "b" -- BIG: Renders text in a "large" font. big :: BuildXML a => a -> a big = tag "big" -- SMALL: Renders text in a "small" font. small :: BuildXML a => a -> a small = tag "small"