{-# LANGUAGE TypeFamilies #-} -- | General XML stuff. -- module Xml ( FromXml(..), parse_opts, pickle_unpickle ) where import Text.XML.HXT.Core ( (>>>), (/>), SysConfigList, XmlPickler(..), hasName, no, readDocument, runX, withPreserveComment, withRemoveWS, withSubstDTDEntities, withValidate, xpickleVal, xunpickleVal, yes ) -- | A typeclass for types which can be converted into an associated -- XML type. The story behind this is long, but basically, we need -- to different types for each XML thingie we're going to import: a -- database type and an XML type. Both Groundhog and HXT are very -- particular about the types that they can use, and there's no way -- to reuse e.g. a type that HXT can pickle in Groundhog. So this -- typeclass gives us a way to get the XML type from the Groundhog -- type. -- -- At first there appears to be an equally-valid approach, getting the -- Groundhog type from the XML one. But Groundhog won't use type family -- instances, so here we are. -- class FromXml a where -- | Each instance a must declare its associated XML type (Xml a) type Xml a :: * -- | And provide a function for getting an (Xml a) out of an "a." to_xml :: a -> Xml a -- | A list of options passed to 'readDocument' when we parse an XML -- document. We don't validate because the DTDs from TSN are -- wrong. As a result, we don't want to keep useless DTDs -- areound. Thus we disable 'withSubstDTDEntities' which, when -- combined with "withValidate no", prevents HXT from trying to read -- the DTD at all. -- parse_opts :: SysConfigList parse_opts = [ withPreserveComment no, withRemoveWS yes, withSubstDTDEntities no, withValidate no ] -- | Given a root element name and a file path, return both the -- original unpickled root "object" and the one that was constructed -- by pickled and unpickling the original. This is used in a number -- of XML tests which pickle/unpickle and then make sure that the -- output is the same as the input. -- -- We return the object instead of an XmlTree (which would save us -- an unpickle call) because otherwise the type of @a@ in the call -- to 'xpickle' would be ambiguous. By returning some @a@s, we allow -- the caller to annotate its type. -- pickle_unpickle :: XmlPickler a => String -> FilePath -> IO ([a], [a]) pickle_unpickle root_element filepath = do -- We need to check only the root message element since -- readDocument produces a bunch of other junk. expected <- runX $ arr_getobj actual <- runX $ arr_getobj >>> xpickleVal xpickle >>> xunpickleVal xpickle return (expected, actual) where arr_getobj = readDocument parse_opts filepath /> hasName root_element >>> xunpickleVal xpickle