{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Zenacy.HTML.Internal.Render
( htmlPrint
, htmlPrintPretty
, htmlRender
, htmlRenderContent
, htmlRenderNodes
, htmlRenderPretty
) where
import Zenacy.HTML.Internal.Core
import Zenacy.HTML.Internal.HTML
import Zenacy.HTML.Internal.Oper
import Data.Monoid
( (<>)
)
import Data.Text
( Text
)
import qualified Data.Text as T
( append
, concat
, empty
, intercalate
, replace
)
import qualified Data.Text.IO as T
( putStrLn
)
data HTMLRenderMode
= HTMLRenderNormal
| HTMLRenderPretty
deriving (Show, Eq, Ord)
htmlPrint :: HTMLNode -> IO ()
htmlPrint = T.putStrLn . htmlRender
htmlPrintPretty :: HTMLNode -> IO ()
htmlPrintPretty = T.putStrLn . htmlRenderPretty
htmlRender :: HTMLNode -> Text
htmlRender = renderModal HTMLRenderNormal
htmlRenderContent :: HTMLNode -> Text
htmlRenderContent = htmlRenderNodes . htmlNodeContent
htmlRenderNodes :: [HTMLNode] -> Text
htmlRenderNodes = T.concat . map htmlRender
htmlRenderPretty :: HTMLNode -> Text
htmlRenderPretty = renderModal HTMLRenderPretty
renderModal :: HTMLRenderMode -> HTMLNode -> Text
renderModal m = go 0 ""
where
go level parent node =
case node of
HTMLDocument _ c ->
join $ map (go level parent) c
HTMLDoctype n p s ->
indent <> renderDoctype n p s
HTMLFragment n c ->
join $ map (go level parent) c
HTMLElement n s a c ->
indent
<> renderElemStart n a
<> if voidTag n
then T.empty
else (if | genLF n c -> "\n"
| oneLine c -> T.empty
| otherwise -> sep)
<> (join $ map (go (if oneLine c then 0 else level') n) c)
<> (if | oneLine c -> T.empty
| null c -> indent
| otherwise -> sep <> indent)
<> renderElemEnd n
HTMLTemplate s a c ->
indent <> renderElemStart tmp a
<> sep <> go level' tmp c
<> sep <> indent <> renderElemEnd tmp
HTMLText t ->
indent <> renderText t parent
HTMLComment c ->
indent <> renderComment c
where
level' = level + 1
join = T.intercalate sep
indent = case m of
HTMLRenderNormal -> T.empty
HTMLRenderPretty -> textBlank level
sep = case m of
HTMLRenderNormal -> T.empty
HTMLRenderPretty -> "\n"
tmp = "template"
voidTag x = elem x
["area", "base", "basefont", "bgsound", "br", "col",
"embed", "frame", "hr", "img", "input", "keygen",
"link", "meta", "param", "source", "track", "wbr"]
genLF x c = elem x ["pre", "textarea", "listing"] && oneText c
oneText (HTMLText {}:[]) = True
oneText _ = False
oneLine x = oneText x || null x
renderDoctype :: Text -> Maybe Text -> Maybe Text -> Text
renderDoctype x y z = "<!DOCTYPE " <> x <> f y <> f z <> ">"
where
f = maybe "" (T.append " ")
renderAttr :: HTMLAttr -> Text
renderAttr (HTMLAttr n v s) =
" " <> n' <> "=\"" <> escapeString True v <> "\""
where
n' = case s of
HTMLAttrNamespaceNone -> n
HTMLAttrNamespaceXLink -> "xlink:" <> n
HTMLAttrNamespaceXML -> "xml:" <> n
HTMLAttrNamespaceXMLNS ->
if n == "xmlns" then n else "xmlns:" <> n
renderAttrList :: [HTMLAttr] -> Text
renderAttrList = T.concat . map renderAttr
renderElemStart :: Text -> [HTMLAttr] -> Text
renderElemStart x y = "<" <> x <> renderAttrList y <> ">"
renderElemEnd :: Text -> Text
renderElemEnd x = "</" <> x <> ">"
renderText :: Text -> Text -> Text
renderText x parent =
if parent `elem` a then x else escapeString False x
where a = ["style", "script", "xmp", "iframe",
"noembed", "noframes", "plaintext"]
renderComment :: Text -> Text
renderComment x = "<!--" <> x <> "-->"
escapeString :: Bool -> Text -> Text
escapeString attributeMode =
f . T.replace "\x00A0" " "
where
f = if attributeMode
then T.replace "\"" """
else T.replace ">" ">"
. T.replace "<" "<"