{-# OPTIONS -fno-warn-orphans #-} {-# LANGUAGE CPP , FlexibleContexts , FlexibleInstances , KindSignatures , ScopedTypeVariables , TypeOperators #-} #if MIN_VERSION_base(4,9,0) {-# LANGUAGE DataKinds #-} #endif #if __GLASGOW_HASKELL__ < 710 {-# LANGUAGE OverlappingInstances #-} #endif module Generics.XmlPickler ( gxpickle , GXmlPickler (..) , formatElement , xpEither ) where import Data.Char (toLower) import Data.Text (Text, pack, unpack) import GHC.Generics import Generics.Deriving.ConNames (ConNames) import Text.XML.HXT.Arrow.Pickle.Schema (scAlt) import Text.XML.HXT.Arrow.Pickle.Xml -- | The generic XmlPickler class. This gives generic xml picklers for -- the functors from 'Generics.Regular'. These are usually not used -- directly. class GXmlPickler f where gxpicklef :: PU a -> PU (f a) -- Void: Used for data types without constructors --instance GXmlPickler I where -- gxpicklef pu = (xpWrap (I, unI) pu) { theSchema = ElemRef "data" } instance XmlPickler a => GXmlPickler (K1 i a) where gxpicklef _ = (K1, unK1) `xpWrap` xpickle instance GXmlPickler U1 where gxpicklef _ = (const U1, const ()) `xpWrap` xpUnit instance (GXmlPickler f, GXmlPickler g) => GXmlPickler (f :+: g) where gxpicklef f = gxpicklef f `xpSum` gxpicklef f instance (GXmlPickler f, GXmlPickler g) => GXmlPickler (f :*: g) where gxpicklef f = (uncurry (:*:), \(a :*: b) -> (a, b)) `xpWrap` (gxpicklef f `xpPair` gxpicklef f) instance GXmlPickler f => GXmlPickler (M1 D c f) where gxpicklef f = (M1, unM1) `xpWrap` gxpicklef f instance (Constructor c, GXmlPickler f) => GXmlPickler (M1 C c f) where gxpicklef f = xpElem (formatElement $ conName (undefined :: M1 C c f p)) ((M1, unM1) `xpWrap` gxpicklef f) instance (Selector c, GXmlPickler f) => GXmlPickler (M1 S c f) where gxpicklef f = optElem ((M1, unM1) `xpWrap` gxpicklef f) (undefined :: M1 S c f p) -- | The generic pickler. Uses a tag for each constructor with the -- lower case constructor name, and a tag for each record field with -- the lower case field name. Most values are pickled using their own -- 'XmlPickler' instance, and 'String's are pickled as possibly empty -- text nodes. gxpickle :: forall a. (Generic a, GXmlPickler (Rep a), ConNames (Rep a)) => PU a gxpickle = (to, from) `xpWrap` gxpicklef (gxpickle :: PU a) -- * Pickling combinators -- | Combine two picklers into a pickler for 'Either'. While pickling, -- check if the either is a 'Left' or 'Right' and use the appropriate -- pickler. During unpickling, first try the first, and if it fails, -- try the second. xpEither :: PU a -> PU b -> PU (Either a b) xpEither ~(PU fl tl sa) ~(PU fr tr sb) = PU (\x st -> case x of Left y -> fl y st Right y -> fr y st) (UP $ \x -> case runUP tl x of -- When the first fails with error message es, try the second (Left (es, _), _) -> case runUP tr x of (Left (es', _), st) -> (Left (es ++ "\n" ++ es', st), st) (Right r, st) -> (Right (Right r), st) (Right r, st) -> (Right (Left r), st)) (sa `scAlt` sb) xpSum :: PU (f r) -> PU (g r) -> PU ((f :+: g) r) xpSum l r = (i, o) `xpWrap` xpEither l r where i (Left x) = L1 x i (Right x) = R1 x o (L1 x) = Left x o (R1 x) = Right x -- * Boolean instance for XmlPickler. instance XmlPickler Bool where xpickle = (toBool, fromBool) `xpWrapEither` xpText toBool :: String -> Either String Bool toBool k | k' == "yes" = Right True | k' == "true" = Right True | k' == "on" = Right True where k' = map toLower k toBool k | k' == "no" = Right False | k' == "false" = Right False | k' == "off" = Right False where k' = map toLower k toBool k = Left ("XmlPickler Bool: unexpected value: " ++ k) fromBool :: Bool -> String fromBool True = "true" fromBool False = "false" -- * Either instance for XmlPickler. instance (XmlPickler a, XmlPickler b) => XmlPickler (Either a b) where xpickle = xpEither xpickle xpickle -- * GXmlPickler instance for String, Text and Maybes. #if __GLASGOW_HASKELL__ >= 710 instance {-# OVERLAPS #-} GXmlPickler (K1 i String) where #else instance GXmlPickler (K1 i String) where #endif gxpicklef _ = (K1, unK1) `xpWrap` xpText0 #if __GLASGOW_HASKELL__ >= 710 instance {-# OVERLAPS #-} GXmlPickler (K1 i Text) where #else instance GXmlPickler (K1 i Text) where #endif gxpicklef _ = (K1 . pack, unpack . unK1) `xpWrap` xpText0 #if __GLASGOW_HASKELL__ >= 710 instance {-# OVERLAPS #-} (XmlPickler a, Selector c) => GXmlPickler (M1 S c (K1 i (Maybe a))) where #else instance (XmlPickler a, Selector c) => GXmlPickler (M1 S c (K1 i (Maybe a))) where #endif gxpicklef _ = (M1 . K1, unK1 . unM1) `xpWrap` xpOption (optElem xpickle (undefined :: M1 S c f p)) #if __GLASGOW_HASKELL__ >= 710 instance {-# OVERLAPS #-} Selector c => GXmlPickler (M1 S c (K1 i (Maybe String))) where #else instance Selector c => GXmlPickler (M1 S c (K1 i (Maybe String))) where #endif gxpicklef _ = (M1 . K1, unK1 . unM1) `xpWrap` xpOption (optElem xpText0 (undefined :: M1 S c f p)) #if __GLASGOW_HASKELL__ >= 710 instance {-# OVERLAPS #-} Selector c => GXmlPickler (M1 S c (K1 i (Maybe Text))) where #else instance Selector c => GXmlPickler (M1 S c (K1 i (Maybe Text))) where #endif gxpicklef _ = (M1 . K1 . fmap pack, fmap unpack . unK1 . unM1) `xpWrap` xpOption (optElem xpText0 (undefined :: M1 S c f p)) -- * Utilities formatElement :: String -> String formatElement = headToLower . stripLeadingAndTrailingUnderscore headToLower :: String -> String headToLower l = case l of [] -> [] (x:xs) -> toLower x : xs stripLeadingAndTrailingUnderscore :: String -> String stripLeadingAndTrailingUnderscore = stripLeadingUnderscore . stripTrailingUnderscore stripLeadingUnderscore :: String -> String stripLeadingUnderscore s = case s of ('_':ls) -> ls ls -> ls stripTrailingUnderscore :: String -> String stripTrailingUnderscore s = case s of "" -> "" [x,'_'] -> [x] (x:xs) -> x : stripTrailingUnderscore xs #if MIN_VERSION_base(4,9,0) optElem :: forall (t :: * -> Meta -> (* -> *) -> * -> *) i (s :: Meta) (f :: * -> *) p a. Selector s => PU a -> t i s f p -> PU a #else optElem :: forall a s (t :: * -> (* -> *) -> * -> *) (f :: * -> *) b. Selector s => PU a -> t s f b -> PU a #endif optElem x y = case formatElement (selName y) of "" -> x n -> n `xpElem` x