wraxml-0.5: src/Text/HTML/WraXML/Tree/HXT.hs
module Text.HTML.WraXML.Tree.HXT
(tidy, format, fromHTMLString, fromHTMLStringMetaEncoding,
errorAnnFromHTMLStringMetaEncoding,
errorAnnFromHTMLStringOpt, ErrorMsg,
getMetaHTTPHeaders, ) where
import qualified Text.XML.HXT.DOM.XmlNode as XmlNode
import qualified Text.XML.HXT.Arrow.XmlState.ErrorHandling as HXTError
import qualified Text.XML.HXT.Core as HXT
import Text.XML.HXT.Core (withInputEncoding, isoLatin1, yes)
import Text.XML.HXT.Arrow.XmlState (runX)
import Text.XML.HXT.DOM.QualifiedName (QName, qualifiedName)
import Text.XML.HXT.DOM.TypeDefs (XNode(..))
import Text.XML.HXT.DOM.ShowXml (xshow)
import qualified Data.Tree.Class as HXTTree
import Data.Tree.NTree.TypeDefs (NTree(NTree))
import qualified Text.XML.WraXML.Tree.HXT as WraHXT
import qualified Text.HTML.WraXML.Tree as HtmlTree
import qualified Text.XML.WraXML.Tree as XmlTree
import qualified Text.XML.Basic.Name.LowerCase as NameLC
import qualified Text.XML.Basic.Name as Name
import qualified Text.XML.Basic.Format as Format
import Text.XML.HXT.Parser.HtmlParsec (isEmptyHtmlTag)
import Control.Category ((>>>))
import Control.Monad (msum)
import Data.Maybe (fromMaybe)
{- |
Tidy a piece of HTML code.
& -> &
< -> <
unquoted tag attribute values: size=-1 -> size="-1"
insert omitted closing tags
-}
tidy :: String -> IO String
tidy input =
fmap (\[str] -> str) $
runX $
ioSLAFromIO (fromHTMLString input)
>>>
HXT.writeDocumentToString [HXT.withOutputHTML]
ioSLAFromIO :: IO b -> HXT.IOSLA s a b
ioSLAFromIO act = HXT.IOSLA $ \s _ -> fmap (\b -> (s,[b])) act
-- * formatting
{- |
Like 'Text.XML.HXT.DOM.XmlTreeFunctions.xshow'
but it shows empty tags the HTML way.
E.g. it emits @<br>@ instead of @<br\/>@,
@<noscript><\/noscript>@ instead of @<noscript\/>@.
Many browsers prefer that.
-}
format :: HXT.XmlTree -> String
format leaf = formatTrees (HXTTree.getChildren leaf) ""
formatTrees :: HXT.XmlTrees -> ShowS
formatTrees = foldr (.) id . map formatTree
-- cf. src/Text/XML/HXT/DOM/XmlTreeFunctions.hs
formatTree :: HXT.XmlTree -> ShowS
formatTree leaf =
case leaf of
(NTree (XPi n al) _) ->
showString "<?"
.
formatQName n
.
(foldr (.) id . map showPiAttr) al
.
showString "?>"
where
showPiAttr :: HXT.XmlTree -> String -> String
showPiAttr a@(NTree (XAttr an) cs) =
if qualifiedName an == HXT.a_value
then Format.blank . formatTrees cs
else formatTree a
showPiAttr _ = id
(NTree (XTag t al) cs) ->
if null cs && isEmptyHtmlTag (qualifiedName t)
then Format.lt . formatQName t . formatTrees al . Format.gt
else Format.lt . formatQName t . formatTrees al . Format.gt
. formatTrees cs
. Format.lt . Format.slash . formatQName t . Format.gt
(NTree (XAttr an) cs) ->
Format.blank . formatQName an . Format.eq .
Format.stringQuoted (formatTrees cs "")
(NTree (XError l e) _) ->
showString "<!-- ERROR (" . showString (show l) . showString "):\n"
. showString e . showString "\n-->"
_ -> (xshow [leaf] ++) -- showXmlTree leaf
formatQName :: QName -> ShowS
formatQName = showString . qualifiedName
-- * parsing and encoding
{- |
Search for a META tag for the encoding of the HTML text.
-}
findMetaEncoding :: String -> IO (Maybe String)
findMetaEncoding str =
do htmlTrees <- xmlTreesFromHTMLString str
return (msum (map HtmlTree.findMetaEncoding
(htmlTrees :: [XmlTree.T () NameLC.T String])))
getMetaHTTPHeaders :: String -> IO [(String, String)]
getMetaHTTPHeaders str =
do htmlTrees <- xmlTreesFromHTMLString str
return (concatMap HtmlTree.getMetaHTTPHeaders
(htmlTrees :: [XmlTree.T () NameLC.T String]))
xmlTreesFromHTMLString ::
(Name.Tag name, Name.Attribute name) =>
String -> IO [XmlTree.T () name String]
xmlTreesFromHTMLString str =
do hxtTree <- fromHTMLString str
-- it will hopefully be only one HTML tree
return $
map (XmlTree.unescape . WraHXT.toXmlTree)
(filter (WraHXT.checkTagName "html" . HXTTree.getNode)
(HXTTree.getChildren hxtTree))
{- |
Guess the encoding from the META-HTTP-EQUIV attribute, if available.
Otherwise fall back to ISO-Latin-1.
-}
fromHTMLStringMetaEncoding :: String -> IO HXT.XmlTree
fromHTMLStringMetaEncoding str =
do enc <- findMetaEncoding str
fromHTMLStringOpt [withInputEncoding $ fromMaybe isoLatin1 enc] str
{-
With no encoding option given,
utf8ToUnicode fails when trying
to interpret ISO-Latin characters as UTF-8 characters.
-}
fromHTMLString :: String -> IO HXT.XmlTree
fromHTMLString = fromHTMLStringOpt [withInputEncoding isoLatin1]
fromHTMLStringOpt :: HXT.SysConfigList -> String -> IO HXT.XmlTree
fromHTMLStringOpt options input =
do (tree,_,_) <- errorAnnFromHTMLStringOpt options input
return tree
type ErrorMsg = (Int,String)
{-# WARNING errorAnnFromHTMLStringMetaEncoding
"error collection does not work currently" #-}
errorAnnFromHTMLStringMetaEncoding ::
String -> IO (HXT.XmlTree, [ErrorMsg], Maybe Int)
errorAnnFromHTMLStringMetaEncoding str = do
enc <- findMetaEncoding str
errorAnnFromHTMLStringOpt [withInputEncoding $ fromMaybe isoLatin1 enc] str
{- |
In earlier version I managed to obtain the error messages.
This does not work anymore and do not know how to achieve this.
-}
{-# WARNING errorAnnFromHTMLStringOpt
"error collection does not work currently" #-}
errorAnnFromHTMLStringOpt ::
HXT.SysConfigList -> String -> IO (HXT.XmlTree, [ErrorMsg], Maybe Int)
errorAnnFromHTMLStringOpt = errorAnnFromHTMLStringInternal
{- |
Adaption of Text.XML.HXT.Parser.MainFunctions.getXmlDocument
-}
errorAnnFromHTMLStringInternal ::
HXT.SysConfigList -> String -> IO (HXT.XmlTree, [ErrorMsg], Maybe Int)
errorAnnFromHTMLStringInternal options contents = do
(root_:errs) <-
runX $
HXT.errorMsgCollect
>>>
HXT.readString (HXT.withParseHTML yes : options) contents
>>>
HXTError.filterErrorMsg
let elvl = XmlNode.getErrorLevel root_
let errMsgs =
map ((\(XError level msg) -> (level, msg)) . HXTTree.getNode) errs
return (root_, errMsgs, elvl)
{-
putStr . xshow =<< run' (Text.XML.HXT.Parser.MainFunctions.parseDocument [(a_source,"lousy.html"), (a_parse_html,v_1)] emptyRoot)
readFile "lousy.html" >>= tidy >>= putStr
-}