{-# LANGUAGE OverloadedStrings, GeneralizedNewtypeDeriving #-} -- | This is a fast non-pretty-printer for turning the internal representation -- of generic structured XML documents into Lazy ByteStrings. -- Like in Text.Xml.HaXml.Pretty, there is one pp function for each type in -- Text.Xml.HaXml.Types, so you can pretty-print as much or as little -- of the document as you wish. module Network.XmlRpc.Pretty (document, content, element, doctypedecl, prolog, cp) where import Prelude hiding (maybe, elem, concat, null, head) import qualified Prelude as P import Data.ByteString.Lazy.Char8 (ByteString(), elem, empty) import qualified Data.ByteString.Lazy.UTF8 as BU import Text.XML.HaXml.Types import Blaze.ByteString.Builder (Builder, fromLazyByteString, toLazyByteString) import Blaze.ByteString.Builder.Char.Utf8 (fromString) import Data.Maybe (isNothing) import Data.Monoid (Monoid, mempty, mconcat, mappend) import qualified GHC.Exts as Ext -- |A 'Builder' with a recognizable empty value. newtype MBuilder = MBuilder { unMB :: Maybe Builder } deriving Monoid -- |'Maybe' eliminator specialized for 'MBuilder'. maybe :: (t -> MBuilder) -> Maybe t -> MBuilder maybe _ Nothing = mempty maybe f (Just x) = f x -- |Nullity predicate for 'MBuilder'. null :: MBuilder -> Bool null = isNothing . unMB -- |Helper for injecting 'ByteString's into 'MBuilder'. fromLBS :: ByteString -> MBuilder fromLBS = MBuilder . Just . fromLazyByteString -- Helper needed when using Data.Binary.Builder. -- fromString :: String -> Builder -- fromString = fromLazyByteString . BU.fromString -- |Support for the OverloadedStrings extension to improve templating -- syntax. instance Ext.IsString MBuilder where fromString "" = mempty fromString s = MBuilder . Just . fromString $ s -- A simple implementation of the pretty-printing combinator interface, -- but for plain ByteStrings: infixr 6 <> infixr 6 <+> infixr 5 $$ -- |Beside. (<>) :: MBuilder -> MBuilder -> MBuilder (<>) = mappend -- |Concatenate two 'MBuilder's with a single space in between -- them. If either of the component 'MBuilder's is empty, then the -- other is returned without any additional space. (<+>) :: MBuilder -> MBuilder -> MBuilder (<+>) b1 b2 | null b2 = b1 | null b1 = b2 | otherwise = b1 <> " " <> b2 -- |Concatenate two 'MBuilder's with a single newline in between -- them. If either of the component 'MBuilder's is empty, then the -- other is returned without any additional newline. ($$) :: MBuilder -> MBuilder -> MBuilder ($$) b1 b2 | null b2 = b1 | null b1 = b2 | otherwise = b1 <> "\n" <> b2 -- |Concatenate a list of 'MBuilder's with a given 'MBuilder' inserted -- between each non-empty element of the list. intercalate :: MBuilder -> [MBuilder] -> MBuilder intercalate sep = aux . filter (not . null) where aux [] = mempty aux (x:xs) = x <> mconcat (map (sep <>) xs) -- |List version of '<+>'. hsep :: [MBuilder] -> MBuilder hsep = intercalate " " -- |List version of '$$'. vcat :: [MBuilder] -> MBuilder vcat = intercalate "\n" hcatMap :: (a -> MBuilder) -> [a] -> MBuilder hcatMap = (mconcat .) . map vcatMap :: (a -> MBuilder) -> [a] -> MBuilder vcatMap = (vcat .) . map -- |``Paragraph fill'' version of 'sep'. fsep :: [MBuilder] -> MBuilder fsep = hsep -- |Bracket an 'MBuilder' with parentheses. parens :: MBuilder -> MBuilder parens p = "(" <> p <> ")" text :: String -> MBuilder text = MBuilder . Just . fromString name :: QName -> MBuilder name = MBuilder . Just . fromString . unQ where unQ (QN (Namespace prefix uri) n) = prefix++":"++n unQ (N n) = n ---- -- Now for the XML pretty-printing interface. -- (Basically copied direct from Text.XML.HaXml.Pretty). -- |Render a 'Document' to a 'ByteString'. document :: Document i -> ByteString content :: Content i -> ByteString element :: Element i -> ByteString doctypedecl :: DocTypeDecl -> ByteString prolog :: Prolog -> ByteString cp :: CP -> ByteString -- Builder variants of exported functions. documentB :: Document i -> MBuilder contentB :: Content i -> MBuilder elementB :: Element i -> MBuilder doctypedeclB :: DocTypeDecl -> MBuilder prologB :: Prolog -> MBuilder cpB :: CP -> MBuilder xmldecl :: XMLDecl -> MBuilder misc :: Misc -> MBuilder sddecl :: Bool -> MBuilder markupdecl :: MarkupDecl -> MBuilder attribute :: Attribute -> MBuilder -- |Run an 'MBuilder' to generate a 'ByteString'. runMBuilder :: MBuilder -> ByteString runMBuilder = aux . unMB where aux Nothing = empty aux (Just b) = toLazyByteString b document = runMBuilder . documentB content = runMBuilder . contentB element = runMBuilder . elementB doctypedecl = runMBuilder . doctypedeclB prolog = runMBuilder . prologB cp = runMBuilder . cpB documentB (Document p _ e m) = prologB p $$ elementB e $$ vcatMap misc m prologB (Prolog x m1 dtd m2) = maybe xmldecl x $$ vcatMap misc m1 $$ maybe doctypedeclB dtd $$ vcatMap misc m2 xmldecl (XMLDecl v e sd) = " text v <> "'" <+> maybe encodingdecl e <+> maybe sddecl sd <+> "?>" misc (Comment s) = "" misc (PI (n,s)) = " text n <+> text s <+> "?>" sddecl sd | sd = "standalone='yes'" | otherwise = "standalone='no'" doctypedeclB (DTD n eid ds) = if P.null ds then hd <> ">" else hd <+> " [" $$ vcatMap markupdecl ds $$ "]>" where hd = " name n <+> maybe externalid eid markupdecl (Element e) = elementdecl e markupdecl (AttList a) = attlistdecl a markupdecl (Entity e) = entitydecl e markupdecl (Notation n) = notationdecl n markupdecl (MarkupMisc m) = misc m elementB (Elem n as []) = "<" <> (name n <+> fsep (map attribute as)) <> "/>" elementB (Elem n as cs) | isText (P.head cs) = "<" <> (name n <+> fsep (map attribute as)) <> ">" <> hcatMap contentB cs <> " name n <> ">" | otherwise = "<" <> (name n <+> fsep (map attribute as)) <> ">" <> hcatMap contentB cs <> " name n <> ">" isText :: Content t -> Bool isText (CString _ _ _) = True isText (CRef _ _) = True isText _ = False attribute (n,v) = name n <> "=" <> attvalue v contentB (CElem e _) = elementB e contentB (CString False s _) = chardata s contentB (CString True s _) = cdsect s contentB (CRef r _) = reference r contentB (CMisc m _) = misc m elementdecl :: ElementDecl -> MBuilder elementdecl (ElementDecl n cs) = " name n <+> contentspec cs <> ">" contentspec :: ContentSpec -> MBuilder contentspec EMPTY = "EMPTY" contentspec ANY = "ANY" contentspec (Mixed m) = mixed m contentspec (ContentSpec c) = cpB c cpB (TagName n m) = name n <> modifier m cpB (Choice cs m) = parens (intercalate "|" (map cpB cs)) <> modifier m cpB (Seq cs m) = parens (intercalate "," (map cpB cs)) <> modifier m modifier :: Modifier -> MBuilder modifier None = mempty modifier Query = "?" modifier Star = "*" modifier Plus = "+" mixed :: Mixed -> MBuilder mixed PCDATA = "(#PCDATA)" mixed (PCDATAplus ns) = "(#PCDATA |" <+> intercalate "|" (map name ns) <> ")*" attlistdecl :: AttListDecl -> MBuilder attlistdecl (AttListDecl n ds) = " name n <+> fsep (map attdef ds) <> ">" attdef :: AttDef -> MBuilder attdef (AttDef n t d) = name n <+> atttype t <+> defaultdecl d atttype :: AttType -> MBuilder atttype StringType = "CDATA" atttype (TokenizedType t) = tokenizedtype t atttype (EnumeratedType t) = enumeratedtype t tokenizedtype :: TokenizedType -> MBuilder tokenizedtype ID = "ID" tokenizedtype IDREF = "IDREF" tokenizedtype IDREFS = "IDREFS" tokenizedtype ENTITY = "ENTITY" tokenizedtype ENTITIES = "ENTITIES" tokenizedtype NMTOKEN = "NMTOKEN" tokenizedtype NMTOKENS = "NMTOKENS" enumeratedtype :: EnumeratedType -> MBuilder enumeratedtype (NotationType n) = notationtype n enumeratedtype (Enumeration e) = enumeration e notationtype :: [[Char]] -> MBuilder notationtype ns = "NOTATION" <+> parens (intercalate "|" (map text ns)) enumeration :: [[Char]] -> MBuilder enumeration ns = parens (intercalate "|" (map nmtoken ns)) defaultdecl :: DefaultDecl -> MBuilder defaultdecl REQUIRED = "#REQUIRED" defaultdecl IMPLIED = "#IMPLIED" defaultdecl (DefaultTo a f) = maybe (const "#FIXED") f <+> attvalue a reference :: Reference -> MBuilder reference (RefEntity er) = entityref er reference (RefChar cr) = charref cr entityref :: [Char] -> MBuilder entityref n = "&" <> text n <> ";" charref :: (Show a) => a -> MBuilder charref c = "&#" <> text (show c) <> ";" entitydecl :: EntityDecl -> MBuilder entitydecl (EntityGEDecl d) = gedecl d entitydecl (EntityPEDecl d) = pedecl d gedecl :: GEDecl -> MBuilder gedecl (GEDecl n ed) = " text n <+> entitydef ed <> ">" pedecl :: PEDecl -> MBuilder pedecl (PEDecl n pd) = " text n <+> pedef pd <> ">" entitydef :: EntityDef -> MBuilder entitydef (DefEntityValue ew) = entityvalue ew entitydef (DefExternalID i nd) = externalid i <+> maybe ndatadecl nd pedef :: PEDef -> MBuilder pedef (PEDefEntityValue ew) = entityvalue ew pedef (PEDefExternalID eid) = externalid eid externalid :: ExternalID -> MBuilder externalid (SYSTEM sl) = "SYSTEM" <+> systemliteral sl externalid (PUBLIC i sl) = "PUBLIC" <+> pubidliteral i <+> systemliteral sl ndatadecl :: NDataDecl -> MBuilder ndatadecl (NDATA n) = "NDATA" <+> text n notationdecl :: NotationDecl -> MBuilder notationdecl (NOTATION n e) = " text n <+> either externalid publicid e <> ">" publicid :: PublicID -> MBuilder publicid (PUBLICID p) = "PUBLICID" <+> pubidliteral p encodingdecl :: EncodingDecl -> MBuilder encodingdecl (EncodingDecl s) = "encoding='" <> text s <> "'" nmtoken :: [Char] -> MBuilder nmtoken s = text s attvalue :: AttValue -> MBuilder attvalue (AttValue esr) = "\"" <> hcatMap attVal esr <> "\"" where attVal = either text reference entityvalue :: EntityValue -> MBuilder entityvalue (EntityValue evs) | containsDoubleQuote evs = "'" <> hcatMap ev evs <> "'" | otherwise = "\"" <> hcatMap ev evs <> "\"" ev :: EV -> MBuilder ev (EVString s) = text s ev (EVRef r) = reference r pubidliteral :: PubidLiteral -> MBuilder pubidliteral (PubidLiteral s) | '"' `elem` s' = "'" <> fromLBS s' <> "'" | otherwise = "\"" <> fromLBS s' <> "\"" where s' = BU.fromString s systemliteral :: SystemLiteral -> MBuilder systemliteral (SystemLiteral s) | '"' `elem` s' = "'" <> fromLBS s' <> "'" | otherwise = "\"" <> fromLBS s' <> "\"" where s' = BU.fromString s chardata, cdsect :: [Char] -> MBuilder chardata s = {-if all isSpace s then empty else-} text s cdsect c = " chardata c <> "]]>" containsDoubleQuote :: [EV] -> Bool containsDoubleQuote evs = any csq evs where csq (EVString s) = '"' `elem` BU.fromString s csq _ = False