mangrove-0.1.0.0: src/Web/Mangrove/Parse/Tree/Foreign.hs
{-# LANGUAGE OverloadedStrings #-}
{-|
Description: Token processing rules within non-HTML content.
Copyright: (c) 2020 Sam May
License: MPL-2.0
Maintainer: ag.eitilt@gmail.com
Stability: stable
Portability: portable
-}
module Web.Mangrove.Parse.Tree.Foreign
( treeForeign
) where
import qualified Control.Monad.Trans.State as N.S
import qualified Data.HashMap.Strict as M
import qualified Data.Maybe as Y
import qualified Data.Text as T
import Web.Willow.DOM
import Web.Mangrove.Parse.Common.Error
import Web.Mangrove.Parse.Tokenize.Common
import Web.Mangrove.Parse.Tree.Common
import Web.Mangrove.Parse.Tree.InBody
import Web.Mangrove.Parse.Tree.Patch
import Web.Willow.Common.Encoding.Character
import Web.Willow.Common.Parser
import Web.Willow.Common.Parser.Switch
import {-# SOURCE #-} Web.Mangrove.Parse.Tree.Dispatcher
-- | __HTML:__
-- @[the rules for parsing tokens in foreign content]
-- (https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign)@
--
-- The parsing instructions for non-HTML content, which follows a simpler
-- recovery model more in line with HTML.
treeForeign :: TreeBuilder TreeOutput
treeForeign = next >>= switch
[ If isNull $ \t' ->
consTreeError UnexpectedNullCharacter <$> insertCharacter (mapTokenOut (const $ Character replacementChar) t')
, If isWhitespace insertCharacter
, If isCharacter $ \t' -> setFramesetNotOk *> insertCharacter t'
, If isComment insertComment
, If isDoctype $ \t' ->
packTreeErrors [UnexpectedDoctype $ tokenDocumentType t'] t'
, If isEOF $ \t' -> push t' *> treeInBody
, If (isStartTag
[ "b"
, "big"
, "blockquote"
, "body"
, "br"
, "center"
, "code"
, "dd"
, "div"
, "dl"
, "dt"
, "em"
, "embed"
, "h1"
, "h2"
, "h3"
, "h4"
, "h5"
, "h6"
, "head"
, "hr"
, "i"
, "img"
, "li"
, "listing"
, "menu"
, "meta"
, "nobr"
, "ol"
, "p"
, "pre"
, "ruby"
, "s"
, "small"
, "span"
, "strong"
, "strike"
, "sub"
, "sup"
, "table"
, "tt"
, "u"
, "ul"
, "var"
]) htmlStartTag
, If (isStartTag ["font"]) $ \t' ->
if any (`elem` ["color", "face", "size"]) (map fst . M.toList . tokenAttributes $ tokenOut t')
then htmlStartTag t'
else anyOtherStartTag t'
, If isAnyStartTag anyOtherStartTag
, If (isEndTag ["script"]) $ \t' -> do
current <- currentNode
state <- N.S.get
if (current >>= elementNamespace) == Just mathMLNamespace
then closeCurrentNode t' <* closeSvgScript
else anyOtherEndTag (openElements state) t'
, If isAnyEndTag $ \t' -> do
state <- N.S.get
anyOtherEndTag (openElements state) t'
]
where anyOtherStartTag t' = case tokenOut t' of
StartTag d -> do
current <- adjustedCurrentNode
let ns = Y.fromMaybe htmlNamespace $ current >>= elementNamespace
d' = case ns of
ns' | mathMLNamespace == ns' -> adjustMathMLAttributes d
ns' | svgNamespace == ns' -> adjustSvgAttributes $ case tagName d of
"altglyph" -> d { tagName = "altGlyph" }
"altglyphdef" -> d { tagName = "altGlyphDef" }
"altglyphitem" -> d { tagName = "altGlyphItem" }
"animatecolor" -> d { tagName = "animateColor" }
"animatemotion" -> d { tagName = "animateMotion" }
"animatetransform" -> d { tagName = "animateTransform" }
"clippath" -> d { tagName = "clipPath" }
"feblend" -> d { tagName = "feBlend" }
"fecolormatrix" -> d { tagName = "feColorMatrix" }
"fecomponenttransfer" -> d { tagName = "feComponentTransfer" }
"fecomposite" -> d { tagName = "feComposite" }
"feconvolvematrix" -> d { tagName = "feConvolveMatrix" }
"fediffuselighting" -> d { tagName = "feDiffuseLighting" }
"fedisplacementmap" -> d { tagName = "feDisplacementMap" }
"fedistantlight" -> d { tagName = "feDistantLight" }
"fedropshadow" -> d { tagName = "feDropShadow" }
"feflood" -> d { tagName = "feFlood" }
"fefunca" -> d { tagName = "feFuncA" }
"fefuncb" -> d { tagName = "feFuncB" }
"fefuncg" -> d { tagName = "feFuncG" }
"fefuncr" -> d { tagName = "feFuncR" }
"fegaussianblur" -> d { tagName = "feGaussianBlur" }
"feimage" -> d { tagName = "feImage" }
"femerge" -> d { tagName = "feMerge" }
"femergenode" -> d { tagName = "feMergeNode" }
"femorphology" -> d { tagName = "feMorphology" }
"feoffset" -> d { tagName = "feOffset" }
"fepointlight" -> d { tagName = "fePointLight" }
"fespecularlighting" -> d { tagName = "feSpecularLighting" }
"fespotlight" -> d { tagName = "feSpotLight" }
"fetile" -> d { tagName = "feTile" }
"feturbulence" -> d { tagName = "feTurbulence" }
"foreignobject" -> d { tagName = "foreignObject" }
"glyphref" -> d { tagName = "glyphRef" }
"lineargradient" -> d { tagName = "linearGradient" }
"radialgradient" -> d { tagName = "radialGradient" }
"textpath" -> d { tagName = "textPath" }
_ -> d
_ -> d
t'' = mapTokenOut (const $ StartTag d') t'
if tagIsSelfClosing d'
then if tagName d' == "script" && ns == svgNamespace
then insertForeignNullElement ns t'' <* closeSvgScript
else insertForeignNullElement ns t''
else insertForeignElement ns t''
_ -> packTreeErrors [] t'
anyOtherEndTag [] t' = packTreeErrors [] t'
anyOtherEndTag es@(e:_) t' = case tokenOut t' of
EndTag d -> if T.map toAsciiLower (elementName $ snd e) == tagName d
then loopEndTag d es t'
else consTreeError UnexpectedElementWithImpliedEndTag <$> loopEndTag d es t'
_ -> packTreeErrors [] t'
loopEndTag d es t' = either id id <$> loopEndTag' d es t'
loopEndTag' _ [] t' = Left <$> packTreeErrors [] t'
loopEndTag' _ [_] t' = Left <$> packTreeErrors [] t'
loopEndTag' d (e:es) t'
| T.map toAsciiLower (elementName $ snd e) == tagName d =
Right <$> closeCurrentNode t'
| otherwise = case es of
(e':_) | elementNamespace (snd e') == Just htmlNamespace -> push t' *> fmap Left dispatchHtml
_ -> do
recurse <- loopEndTag' d es t'
case recurse of
l@(Left _) -> return l
Right clear -> do
close <- closeCurrentNode_
return . Right $ close ++| clear
tokenAttributes (StartTag d) = tagAttributes d
tokenAttributes (EndTag d) = tagAttributes d
tokenAttributes _ = M.empty
htmlStartTag t' = do
isFragment <- inFragment
consTreeError UnexpectedHtmlElementInForeignContent <$> if isFragment
then anyOtherStartTag t'
else do
push t'
close <- closeCurrentNode_
clear <- clearToIntegration
packTree_ $ close ++ clear
clearToIntegration = do
current <- currentNode
let mathML = maybe False atMathMLIntegration current
html = maybe False atHtmlIntegration current
ns = Y.fromMaybe htmlNamespace $ current >>= elementNamespace
if mathML || html || ns == htmlNamespace
then return []
else do
close <- closeCurrentNode_
clear <- clearToIntegration
return $ close ++ clear
closeSvgScript = undefined