module Text.XML.Light.Output
( showTopElement, showContent, showElement, showCData, showQName, showAttr
, ppTopElement, ppContent, ppElement
, ppcTopElement, ppcContent, ppcElement
, ConfigPP
, defaultConfigPP, prettyConfigPP
, useShortEmptyTags, useExtraWhiteSpace
, tagEnd, xml_header
) where
import Text.XML.Light.Types
import Data.Char
import Data.List ( isPrefixOf )
xml_header :: String
xml_header = "<?xml version='1.0' ?>"
data ConfigPP = ConfigPP
{ shortEmptyTag :: QName -> Bool
, prettify :: Bool
}
defaultConfigPP :: ConfigPP
defaultConfigPP = ConfigPP { shortEmptyTag = const True
, prettify = False
}
useShortEmptyTags :: (QName -> Bool) -> ConfigPP -> ConfigPP
useShortEmptyTags p c = c { shortEmptyTag = p }
useExtraWhiteSpace :: Bool -> ConfigPP -> ConfigPP
useExtraWhiteSpace p c = c { prettify = p }
prettyConfigPP :: ConfigPP
prettyConfigPP = useExtraWhiteSpace True defaultConfigPP
ppTopElement :: Element -> String
ppTopElement = ppcTopElement prettyConfigPP
ppElement :: Element -> String
ppElement = ppcElement prettyConfigPP
ppContent :: Content -> String
ppContent = ppcContent prettyConfigPP
ppcTopElement :: ConfigPP -> Element -> String
ppcTopElement c e = unlines [xml_header,ppcElement c e]
ppcElement :: ConfigPP -> Element -> String
ppcElement c e = ppElementS c "" e ""
ppcContent :: ConfigPP -> Content -> String
ppcContent c x = ppContentS c "" x ""
ppContentS :: ConfigPP -> String -> Content -> ShowS
ppContentS c i x xs = case x of
Elem e -> ppElementS c i e xs
Text t -> ppCDataS c i t xs
CRef r -> showCRefS r xs
ppElementS :: ConfigPP -> String -> Element -> ShowS
ppElementS c i e xs = i ++ (tagStart (elName e) (elAttribs e) $
case elContent e of
[] | "?" `isPrefixOf` qName name -> " ?>" ++ xs
| shortEmptyTag c name -> " />" ++ xs
[Text t] -> ">" ++ ppCDataS c "" t (tagEnd name xs)
cs -> '>' : nl ++ foldr ppSub (i ++ tagEnd name xs) cs
where ppSub e1 = ppContentS c (sp ++ i) e1 . showString nl
(nl,sp) = if prettify c then ("\n"," ") else ("","")
)
where name = elName e
ppCDataS :: ConfigPP -> String -> CData -> ShowS
ppCDataS c i t xs = i ++ if cdVerbatim t /= CDataText || not (prettify c)
then showCDataS t xs
else foldr cons xs (showCData t)
where cons :: Char -> String -> String
cons '\n' ys = "\n" ++ i ++ ys
cons y ys = y : ys
showTopElement :: Element -> String
showTopElement c = xml_header ++ showElement c
showContent :: Content -> String
showContent c = ppContentS defaultConfigPP "" c ""
showElement :: Element -> String
showElement c = ppElementS defaultConfigPP "" c ""
showCData :: CData -> String
showCData c = ppCDataS defaultConfigPP "" c ""
showCRefS :: String -> ShowS
showCRefS r xs = '&' : r ++ ';' : xs
showCDataS :: CData -> ShowS
showCDataS cd =
case cdVerbatim cd of
CDataText -> escStr (cdData cd)
CDataVerbatim -> showString "<![CDATA[" . escCData (cdData cd)
. showString "]]>"
CDataRaw -> \ xs -> cdData cd ++ xs
escCData :: String -> ShowS
escCData (']' : ']' : '>' : cs) = showString "]]]]><![CDATA[>" . escCData cs
escCData (c : cs) = showChar c . escCData cs
escCData [] = id
escChar :: Char -> ShowS
escChar c = case c of
'<' -> showString "<"
'>' -> showString ">"
'&' -> showString "&"
'"' -> showString """
'\'' -> showString "'"
_ | isPrint c || c == '\n' -> showChar c
| otherwise -> showString "&#" . shows oc . showChar ';'
where oc = ord c
escStr :: String -> ShowS
escStr cs rs = foldr escChar rs cs
tagEnd :: QName -> ShowS
tagEnd qn rs = '<':'/':showQName qn ++ '>':rs
tagStart :: QName -> [Attr] -> ShowS
tagStart qn as rs = '<':showQName qn ++ as_str ++ rs
where as_str = if null as then "" else ' ' : unwords (map showAttr as)
showAttr :: Attr -> String
showAttr (Attr qn v) = showQName qn ++ '=' : '"' : escStr v "\""
showQName :: QName -> String
showQName q = pre ++ qName q
where pre = case qPrefix q of
Nothing -> ""
Just p -> p ++ ":"