mangrove-0.1.0.0: src/Web/Mangrove/Parse/Tree/InHead.hs
{-# LANGUAGE OverloadedStrings #-}
{-|
Description: Token processing rules within the @\<head\>@ section.
Copyright: (c) 2020 Sam May
License: MPL-2.0
Maintainer: ag.eitilt@gmail.com
Stability: stable
Portability: portable
-}
module Web.Mangrove.Parse.Tree.InHead
( treeInHead
) where
import qualified Control.Applicative as A
import qualified Control.Monad as N
import qualified Control.Monad.Trans.State as N.S
import qualified Data.Bifunctor as F.B
import qualified Data.ByteString.Short as BS.SH
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Web.Mangrove.Parse.Common.Error
import Web.Mangrove.Parse.Tokenize.Common
import Web.Mangrove.Parse.Tree.Common
import Web.Mangrove.Parse.Tree.InText
import Web.Mangrove.Parse.Tree.Patch
import Web.Willow.Common.Encoding
import Web.Willow.Common.Encoding.Character
import Web.Willow.Common.Encoding.Labels
import Web.Willow.Common.Encoding.Sniffer
import Web.Willow.Common.Parser
import Web.Willow.Common.Parser.Switch
import {-# SOURCE #-} Web.Mangrove.Parse.Tree.InBody
import Control.Applicative ( (<|>) )
-- | __HTML:__
-- @[the "in head" insertion mode]
-- (https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead)@
--
-- The parsing instructions corresponding to the 'InHead' section of the state
-- machine.
treeInHead :: TreeBuilder TreeOutput
treeInHead = next >>= switch
[ If isWhitespace insertCharacter
, If isComment insertComment
, If isDoctype $ \t' ->
packTreeErrors [UnexpectedDoctype $ tokenDocumentType t'] t'
, If (isStartTag ["html"]) $ \t' -> do
push t'
treeInBody
, If (isStartTag ["base", "basefont", "bgsound", "link"]) insertNullElement
, If (isStartTag ["meta"]) $ \t' -> do
insert <- insertNullElement t'
change' <- A.optional $ changeEncoding t'
return $ case change' of
Just change -> insert |++| change
Nothing -> insert
, If (isStartTag ["title"]) genericRCDataElement
, If (isStartTag ["noscript"]) $ \t' -> do
state <- N.S.get
if scriptingEnabled state
then genericRawTextElement t'
else do
switchMode InHeadNoscript
insertElement t'
, If (isStartTag ["noframes", "style"]) genericRawTextElement
, If (isStartTag ["script"]) $ \t' -> do
N.S.modify $ \state -> state
{ originalInsertionMode = Just $ insertionMode state
}
switchMode InText
insert <- insertElement . mapTokenState t' $ \state -> state
{ currentState = ScriptDataState
}
return insert
, If (isEndTag ["head"]) $ \t' -> do
switchMode AfterHead
packTree t' softCloseCurrentNode_
, If (isEndTag ["body", "html", "br"]) anythingElse
, If (isStartTag ["template"]) $ \t' -> do
insert <- insertElement t'
insertFormattingMarker
setFramesetNotOk
switchMode InTemplate
pushTemplateMode InTemplate
return insert
, If (isEndTag ["template"]) $ \t' -> do
generate <- generateEndTags thoroughlyImpliedEndTags
current <- currentNode
let errF = case nodeIsElement "template" <$> current of
Just True -> id
_ -> consTreeError_ UnexpectedElementWithImpliedEndTag
clear <- closeElement "template"
clearFormattingElements
popTemplateMode
resetInsertionMode
packTree t' $ errF generate ++ clear
, If (isStartTag ["head"]) $ packTreeErrors [NestedSingletonElement]
, If isAnyEndTag $ \t' ->
packTreeErrors [UnmatchedEndTag $ tokenElement t'] t'
, Else anythingElse
]
where anythingElse t' = do
push t'
switchMode AfterHead
packTree_ softCloseCurrentNode_
changeEncoding t' = do
let d = tokenTag t'
state = tokenState t' >>= decoderState . fst
enc' <- maybe A.empty return $ do
e <- changeEncodingCharset d <|> changeEncodingContentType d
return $ case e of
Utf16be -> Utf8
Utf16le -> Utf8
UserDefined -> Windows1252
enc -> enc
case state of
Just s -> case decoderEncoding s of
Utf16be -> putDecoderState t' $ setEncodingCertain Utf16be s
Utf16le -> putDecoderState t' $ setEncodingCertain Utf16be s
enc | enc == enc' -> putDecoderState t' $ setEncodingCertain enc' s
_ -> case decoderConfidence s of
Tentative _ rec | not $ encodingEquivalent rec enc' -> do
_ <- putDecoderState t' $ initialDecoderState enc'
restartParsing $ streamStart rec
_ -> putDecoderState t' $ initialDecoderState enc'
Nothing -> putDecoderState t' $ initialDecoderState enc'
changeEncodingCharset d = do
charset <- M.lookup "charset" $ tagAttributes d
lookupEncoding charset
changeEncodingContentType d = do
httpEquiv <- M.lookup "http-equiv" $ tagAttributes d
N.unless (T.map toAsciiLower httpEquiv == "content-type") A.empty
content <- M.lookup "content" $ tagAttributes d
extractEncoding $ T.encodeUtf8 content
encodingEquivalent rec enc = uncurry (==) .
F.B.bimap (map $ parseChar enc) (map T.singleton) . unzip . M.toList $ parsedChars rec
parseChar enc = fst . decode' (initialDecoderState enc) . BS.SH.fromShort
putDecoderState t' decState =
flip packTree [] . mapTokenState' t' . fmap . F.B.first $ \state -> state
{ decoderState_ = Right $ Just decState
}