packages feed

tofromxml (empty) → 0.1.0.0

raw patch · 7 files changed

+572/−0 lines, 7 filesdep +arraydep +basedep +bytestringsetup-changed

Dependencies added: array, base, bytestring, containers, filepath, hexpat, hexpat-pickle, tofromxml

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Robert Reitmeier
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Robert Reitmeier nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ Text/XML/ToFromXML.hs view
@@ -0,0 +1,372 @@+{-# LANGUAGE DeriveGeneric,DefaultSignatures,UndecidableInstances,ScopedTypeVariables,+	OverlappingInstances,TypeOperators,FlexibleContexts,FlexibleInstances #-}+{-# OPTIONS_GHC -fcontext-stack=50 #-}++{-|+Module      : Text.XML.ToFromXML+Description : A library for reading and writing Haskell data from/to XML files+Copyright   : (C) Robert Reitmeier, 2014+License     : GPL-3+Maintainer  : concat ["public","< a t >","thinking","-","machines",".","net"]+Stability   : experimental+Portability : n/a++In order to write a Haskell value to an XML file, the value's type just has to be an instance of 'Generic'+(usually one would use the @deriving 'Generic'@ clause in the @data@ defintion).+Then, the 'writeToXMLFile' will write an XML file representing the value.+This value can be read again using 'readFromXMLFile'. See the following example+(pragma syntax below is broken, how can one correctly incorporate comments in a haddock code block?):++>{ -# LANGUAGE DeriveGeneric #- }+>{ -# OPTIONS_GHC -fcontext-stack=50 #- }+>+>import Text.XML.ToFromXML+>-- GHC.Generics is exported by ToFromXML+>+>data Test = Test { str::String, something::(Int,Char) }+>	deriving (Generic,Show,Eq)+>+>main = do+>	let test = Test "abc" (42,'z')+>	writeToXMLFile "test.xml" test+>	putStrLn $ "writeToXMLFile : " ++ show test+>+>	-- readFromXMLFile's return type can be inferred in this example,+>	-- otherwise it would have to be declared+>	test' <- readFromXMLFile "test.xml"+>	putStrLn $ "readFromXMLFile: " ++ show test'+>	+>	putStrLn $ if test==test' then "OK." else "ERROR!"++Remark: Both 'writeToXMLFile' and 'readFromXMLFile' require the value type+@a@'s generic represention @Rep a@ be an instance of @GFromToXML@, but this is automatically fulfilled.++The generated XML file is++><?xml version="1.0" encoding="UTF-8"?>+><CONSTRUCTOR name="Test">+>  <ARG-1 selector="str">abc</ARG-1>+>  <ARG-2 selector="something">+>    <PAIR>+>      <FIRST>42</FIRST>+>      <SECOND>z</SECOND>+>    </PAIR>+>  </ARG-2>+></CONSTRUCTOR>++The general intention of this module is to keep the generated XML as intuitive and+easy to read as possible. For example, we do flatten nested pairs used by "GHC.Generics"+to represent n-tuples, and TABs and CRs are used to encode long text,+so it keeps its formatting (unfortunately, CDATA sections are not supported with+the Node types used in "Text.XML.Expat.Pickle").++One might need to increase the context stack size with the @-fcontext-stack@ option+when compiling code using this module.++A test suite is included in the package, see @TestToFromXML.hs@ in the package's test directory.+-}+module Text.XML.ToFromXML (+	ToFromXML(..),+	toXML,fromXML,fromXMLEither,+	readFromXMLFile,writeToXMLFile,+	module GHC.Generics+	) where++import GHC.Generics++import qualified Data.ByteString as BS++import Text.XML.Expat.Pickle+import Text.XML.Expat.Tree+import qualified Text.XML.Expat.Format as Format++import Text.Printf++import Data.Char+import Data.Word+import Data.Ratio+import Data.Int+import qualified Data.Set as Set+import qualified Data.Map as Map+import qualified Data.IntMap as IntMap+import Data.Complex+import Data.Array.IArray++-- | For the picklers, are using nodes with both tag and text being Strings.+type Pickler a = PU (ListOf (UNode String)) a++class GToFromXML f where+	gXMLPickler :: Pickler (f p)++{-|+We do not want our XML to be cluttered with nested tuples, so we flatten+these in the XML representation.+-}+class PickleProdN f where+	-- | Given the current index in the flattened tuple, pickleProdN returns the incremented index and the pickler.+	pickleProdN :: Int -> (Int,Pickler (f p))++{-|+Pickling a product is done by first pickling the first component, and then pickling the second+component with the current index @i1@ returned by the first @pickleProdN i@.+The current index @i2@ after @pickleProdN i1@ is returned.+-}+instance (PickleProdN f1,PickleProdN f2) => PickleProdN (f1 :*: f2) where+	pickleProdN i = (i2,xpWrap (uncurry (:*:),\ (a :*: b) -> (a,b)) $ xpPair p1 p2) where+		(i1,p1) = pickleProdN i+		(i2,p2) = pickleProdN i1++argTagNames = [ "ARG-" ++ show i | i <- [1..] ]++instance (GToFromXML f) => PickleProdN (M1 S NoSelector f) where+	pickleProdN i = (i+1,xpElemNodes (argTagNames!!i) gXMLPickler)++{-|+For constructor arguments, one can also leave out the selector attribute+(like Haskell syntax allows leaving out record syntax).+This makes it easier to manually write data XML.+-}+instance (GToFromXML f,Selector s) => PickleProdN (M1 S s f) where+	pickleProdN i = (i+1,picklearg) where+		selname = selName (undefined :: M1 S s f p)+		picklearg = xpAlt (\_->0) [+			xpElemWithAttr (argTagNames!!i) "selector" selname gXMLPickler,+			xpElemNodes (argTagNames!!i) gXMLPickler ]++instance (PickleProdN (f1 :*: f2)) => GToFromXML (f1 :*: f2) where+	gXMLPickler = snd (pickleProdN 0)++instance (GToFromXML f1,GToFromXML f2) => GToFromXML (f1 :+: f2) where+	gXMLPickler = xpAlt selfun [+		xpWrap (L1,\ (L1 x) -> x) gXMLPickler,+		xpWrap (R1,\ (R1 x) -> x) gXMLPickler ] where+		selfun (L1 _) = 0+		selfun (R1 _) = 1++{-|+We won't construct a tag for @M1 D@, since it is unnecessary and cluttering the XML.+Although, it might add to safety...+-}+instance (GToFromXML f,Datatype d) => GToFromXML (M1 D d f) where+	gXMLPickler = xpWrap (M1,unM1) gXMLPickler++{-|+The CONSTRUCTOR tag contains a name attribute with the constructor's name.+Strictly speaking it is unnecessary to encode the name as well, but improves readability+and might add to safety...+-}+instance (GToFromXML f,Constructor c) => GToFromXML (M1 C c f) where+	gXMLPickler = xpWrap (M1,unM1) $ xpElemWithAttr "CONSTRUCTOR" "name" conname gXMLPickler where+		conname = conName (undefined :: M1 C c f p)++-- | No tag created for @M1 S NoSelector@, this is handled in 'PickleProdN'.+instance (GToFromXML f) => GToFromXML (M1 S NoSelector f) where+	gXMLPickler = xpWrap (M1,unM1) gXMLPickler++-- | No tag created for @M1 S s@, this is handled in 'PickleProdN'.+instance (GToFromXML f,Selector s) => GToFromXML (M1 S s f) where+	gXMLPickler = xpWrap (M1,unM1) gXMLPickler++-- | A helper function injecting an attribute with a given/fixed value in a tag.+xpElemWithAttr tag attrname attrval pickler = xpWrap (snd,\b->((),b)) $+	xpElem tag (xpAttrFixed attrname attrval) pickler++{-+@V1@ is not an instance of 'GToFromXML', because we can't serialize data of an empty type since there+are no constructors. It shouldn't be necessary to serialize an empty type, so @V1@ should be rejected by the type checker.+-}++instance GToFromXML U1 where+	gXMLPickler = xpLift U1++instance (ToFromXML a) => GToFromXML (K1 R a) where+	gXMLPickler = xpWrap (K1,unK1) xMLPickler++{-|+The class 'ToFromXML' declares that there is a pickler for the instance type+wrapped by the generic representation's @K1@ constructor.+There are instances for data types that we do want to encode in more precise way, not+following the generic pattern. For example, we want to encode unit @()@ as a+distinct tag @\<UNIT\/\>@, not as text content generated with show/read.+We give a default signature and definition which we can use conveniently by+just stating @instance ToFromXML Word32@ for numeric types, for example.++Special chars are represented in XML by Haskell's escape sequences.+A char is not embraced by single quotes in the tag content (as it would be using the default instance).++Since unfortunately CDATA sections are not supported with+the Node types used in "Text.XML.Expat.Pickle" we are relying on,+a String has to be represented in XML as a tag's text content, using the common Haskell escape sequences.+For better readability and prevention of very long lines in the XML file, after each @'\r'@, @'\n'@ and @'\t'@+the same character is inserted unescaped after the character's escape sequence.+The unescaped character is removed again when parsing the String.+For example, @"abc\ndef"@ will be written in the XML file as @"abc\\n\ndef"@,+with the LF escaped (i.e. @"\\n"@) and followed by a real LF (@'\n'@).+Injecting line breaks and tabs makes long text much more readable und editable by humans+(which is one of the original purposes of XML).+The real LF will be filtered out again while parsing the XML file String.++-}+class ToFromXML a where+	-- | An instance of 'ToFromXML' provides a pickler for type @a@.+	xMLPickler :: Pickler a+	-- | The default signature of 'xMLPickler' requires 'Read' and 'Show' instances+	default xMLPickler :: (Read a,Show a) => Pickler a+	-- | The default definition via 'xpPrim' uses show and read to convert the value to XML tag text content.+	xMLPickler = xpContent xpPrim++instance ToFromXML () where+	xMLPickler = xpElemNodes "UNIT" xpUnit++instance (ToFromXML a) => ToFromXML [a] where+	xMLPickler = xpElemNodes "LIST" $ xpList0 $ xpElemNodes "ITEM" xMLPickler++instance ToFromXML Int+instance ToFromXML Int8+instance ToFromXML Int16+instance ToFromXML Int32+instance ToFromXML Int64+instance ToFromXML Integer+instance ToFromXML Word+instance ToFromXML Word8+instance ToFromXML Word16+instance ToFromXML Word32+instance ToFromXML Word64+instance ToFromXML Float+instance ToFromXML Double+instance (Show a,Read a,Integral a) => ToFromXML (Ratio a)+instance (Show a,Read a) => ToFromXML (Complex a)++instance ToFromXML Bool where+	xMLPickler = xpAlt selfun [+		xpElemNodes "TRUE"  $ xpLift True,+		xpElemNodes "FALSE" $ xpLift False ] where+		selfun True  = 0+		selfun False = 1++instance ToFromXML Char where+	xMLPickler = xpContent $ xpWrap ( fst.head.readLitChar, (`showLitChar` "") ) xpText++instance ToFromXML String where+	xMLPickler = xpContent pickleContentString++skipPickleString = (`elem` ['\r','\n','\t'])++{-|+Pickles a string to/from text content with escaping +-}+pickleContentString :: PU String String+pickleContentString = xpWrap ( readLitString, showLitString ) xpText0 where+	readLitString "" = ""+	readLitString (c:ss) | skipPickleString c = readLitString ss+	readLitString s  = let [(c,ss)] = readLitChar s in c : readLitString ss+	showLitString "" = ""+	showLitString (c:ss) | skipPickleString c = showLitChar c $ c : showLitString ss+	showLitString (c:ss) = showLitChar c $ showLitString ss++{-+Pickler for a Map.+Didn't use 'xpMap' because @show@ing keys as attributes might be inconvenient/unreadable for more complex key types.+-}+instance (Ord k,ToFromXML k,ToFromXML v) => ToFromXML (Map.Map k v) where+	xMLPickler = xpElemNodes "MAP" $ xpWrap (Map.fromList,Map.toList) $ xpList $ +		xpElemNodes "ASSOC" $ xpPair+			(xpElemNodes "KEY"  xMLPickler)+			(xpElemNodes "ELEM" xMLPickler)++instance (ToFromXML v) => ToFromXML (IntMap.IntMap v) where+	xMLPickler = xpElemNodes "INTMAP" $ xpWrap (IntMap.fromList,IntMap.toList) $ xpList $ +		xpElem "ELEM" (xpAttr "index" xpPrim) xMLPickler++-- Didn't use an attribute for the dimension of the tuple because this is not checkable by a schema.+instance (ToFromXML a,ToFromXML b) => ToFromXML (a,b) where+	xMLPickler = xpElemNodes "PAIR" $ xpPair (xpComponent 0) (xpComponent 1)++instance (ToFromXML a,ToFromXML b,ToFromXML c) => ToFromXML (a,b,c) where+	xMLPickler = xpElemNodes "TRIPLE" $ xpTriple (xpComponent 0) (xpComponent 1) (xpComponent 2)++instance (ToFromXML a,ToFromXML b,ToFromXML c,ToFromXML d) => ToFromXML (a,b,c,d) where+	xMLPickler = xpElemNodes "QUADRUPLE" $ xp4Tuple (xpComponent 0) (xpComponent 1) (xpComponent 2) (xpComponent 3)++instance (ToFromXML a,ToFromXML b,ToFromXML c,ToFromXML d,ToFromXML e) => ToFromXML (a,b,c,d,e) where+	xMLPickler = xpElemNodes "QUINTUPLE" $ xp5Tuple (xpComponent 0) (xpComponent 1) (xpComponent 2) (xpComponent 3) (xpComponent 4)++instance (ToFromXML a,ToFromXML b,ToFromXML c,ToFromXML d,ToFromXML e,ToFromXML f) => ToFromXML (a,b,c,d,e,f) where+	xMLPickler = xpElemNodes "SEXTUPLE" $ xp6Tuple (xpComponent 0) (xpComponent 1) (xpComponent 2) (xpComponent 3) (xpComponent 4) (xpComponent 5)++-- | Pickle a tuple's i\'th component. The component tag names are distinct, otherwise hexpat-pickle confuses this with a list and gives a parse error.+xpComponent :: (ToFromXML a) => Int -> Pickler a+xpComponent i = xpElemNodes (componentnames!!i) xMLPickler where+	componentnames = [ "FIRST","SECOND","THIRD","FOURTH","FIFTH","SIXTH" ]++instance (ToFromXML a) => ToFromXML (Maybe a) where+	xMLPickler = xpAlt selfun [+		xpElemNodes "NOTHING" $ xpLift Nothing,+		xpElemNodes "JUST"    $ xpWrap (Just, \ (Just a) -> a ) xMLPickler ] where+		selfun Nothing  = 0+		selfun (Just _) = 1++instance (ToFromXML a,ToFromXML b) => ToFromXML (Either a b) where+	xMLPickler = xpAlt selfun [+		xpElemNodes "LEFT"  $ xpWrap ( Left,  \ (Left  a) -> a ) xMLPickler,+		xpElemNodes "RIGHT" $ xpWrap ( Right, \ (Right b) -> b ) xMLPickler ] where+		selfun (Left  _) = 0+		selfun (Right _) = 1++instance (ToFromXML a,Ord a) => ToFromXML (Set.Set a) where+	xMLPickler = xpElemNodes "SET" $ xpWrap (Set.fromList,Set.toList) $ xpList $+		xpElemNodes "ELEM" xMLPickler++-- TODO: Insert ToFromXML instances for date and time types, maybe in conformance to XSD types?++-- Here we assume that array index types (usually 'Int') are sufficiently simple to be represented as string in an attribute.+instance (Ix i,Show i,Read i,ToFromXML e) => ToFromXML (Array i e) where+	xMLPickler = xpWrap (list2arr,arr2list) $ xpElem "ARRAY" xpbounds $ xpList $+		xpElemNodes "ELEM" xMLPickler where+			xpbounds = xpPair (xpAttr "lowerBound" xpPrim) (xpAttr "upperBound" xpPrim)+			arr2list arr = (bounds arr,elems arr)+			list2arr (bounds,arrelems) = listArray bounds arrelems++-- This is the catch-all instance, leading to the generic @Rep a@ representation+instance (Generic a,GToFromXML (Rep a)) => ToFromXML a where+	xMLPickler = xpWrap (to,from) gXMLPickler++{-|+Converts generic Haskell data to a (strict) 'ByteString' containing the XML representation.+In most cases, the data type would probably be @deriving 'Generic'@ using the @DeriveGeneric@ language pragma.+-}+toXML :: (Generic a,GToFromXML (Rep a)) => a -> BS.ByteString+toXML x = Format.format' $ Format.indent 2 $ pickleTree (xpRoot gXMLPickler) (from x)++{-|+Construct generic Haskell data from a (strict) 'ByteString' containing the XML representation.+fromXMLEither will return 'Left' in case of a 'XMLParseError', 'Right' otherwise.+-}+fromXMLEither :: (GToFromXML f) => BS.ByteString -> Either String (f p)+fromXMLEither bs = case parse' defaultParseOptions bs of+	Left (XMLParseError errmsg loc) ->+		Left $ printf "XMLParseError at %s:\n    %s" (show loc) errmsg+	Right tree ->+		unpickleTree' (xpRoot gXMLPickler) tree++{-|+Convenience function wrapping around 'fromXMLEither', throwing an error in case of a parsing error.+-}+fromXML :: (Generic a,GToFromXML (Rep a)) => BS.ByteString -> a+fromXML bs = case fromXMLEither bs of+	Left  errmsg -> error errmsg+	Right x      -> to x++{-|+Action writing an XML representation of generic Haskell data to a file.+The underlying writeFile operation is strict.+-}+writeToXMLFile :: (Generic a,GToFromXML (Rep a)) => FilePath -> a -> IO ()+writeToXMLFile filepath a = BS.writeFile filepath $ toXML a++{-|+Action reading generic Haskell data from an XML file.+The underlying readFile operation is strict.+-}+readFromXMLFile :: (Generic a,GToFromXML (Rep a)) => FilePath -> IO a+readFromXMLFile filepath = BS.readFile filepath >>= return . fromXML
+ test/Example.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE DeriveGeneric #-}
+{-# OPTIONS_GHC -fcontext-stack=50 #-}
+
+import Text.XML.ToFromXML
+-- GHC.Generics is exported by ToFromXML
+
+data Test = Test { str::String, something::(Int,Char) }
+	deriving (Generic,Show,Eq)
+
+main = do
+	let test = Test "abc" (42,'z')
+	writeToXMLFile "test.xml" test
+	putStrLn $ "writeToXMLFile : " ++ show test
+
+	-- readFromXMLFile's return type can be inferred in this example,
+	-- otherwise it would have to be declared
+	test' <- readFromXMLFile "test.xml"
+	putStrLn $ "readFromXMLFile: " ++ show test'
+	
+	putStrLn $ if test==test' then "OK." else "ERROR!"
+ test/TestToFromXML.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE DeriveGeneric #-}
+{-# OPTIONS_GHC -fcontext-stack=50 #-}
+
+module Main where
+
+import Text.XML.ToFromXML
+
+import System.Exit
+import Data.Char
+import qualified Data.Map.Lazy as Map
+import qualified Data.IntMap as IntMap
+import qualified Data.Set as Set
+import Data.Array.IArray
+import Data.Complex
+import Data.Ratio
+import Text.Printf
+import System.FilePath
+
+testTest :: (Eq i,Eq e,Eq a,Ix i,Ord a,Show a,Show i,Read i,Show e,ToFromXML e,ToFromXML a) => (Int,Test a i e) -> IO Bool
+testTest (i,test) = do
+	putStrLn $ "\n-------------------------\nTest " ++ show i
+
+	let testfilename = "Test" ++ show i ++ ".xml"
+
+	writeToXMLFile testfilename test
+
+	test' <- readFromXMLFile testfilename
+
+	rc <- case test==test' of
+		True -> putStrLn "OK." >> return ExitSuccess
+		False -> do
+			putStrLn $ printf "/= ERROR!\nWRITTEN: %s\n/=\nREAD   : %s" (show test) (show test')
+			return $ ExitFailure 1
+	exitWith rc
+
+data Test a i e =
+	Test1 Int Char () |
+	Test2 { str::String, subtest::(Test a i e) } |
+	Test3 [(String,Int)] |
+	Test4 [Test a i e] |
+	Test5 |
+	Test6 (Float,Double,Bool) |
+	Test7 [Int] |
+	Test8 (Map.Map String (Test a i e)) |
+	Test9 (Maybe (Test a i e)) (Maybe Int) (Either (Test a i e) Char) (Either (Test a i e) Int) |
+	Test10 (Set.Set a) |
+	Test11 (Array i e) |
+	Test12 (Ratio Int) (Complex Double) |
+	Test13 (String,Int) |
+	Test14 (Int,Int) |
+	Test15 (IntMap.IntMap (Test () Int ())) |
+	Test16 [TestSel] |
+	Test17 { first::String, second::Int, third::Float } |
+	Test18 Int Char Bool String |
+	Test19 Int Char Bool String Int |
+	Test20 Int Char Bool (String,Char) Int Float |
+	Test21 Int Char Bool (String,Char,Int) Int Float Int Double Bool Char
+	deriving (Show,Eq,Generic)
+
+data TestSel = TestSel1 Int | TestSel2 Char | TestSel3 TestSel | TestSel4 (Test () Int ()) | TestSel5
+	deriving (Show,Eq,Generic)
+
+main = do
+	longstring <- readFile $ "test" </> "TestToFromXML.hs"
+	oks <- sequence $ map testTest $ zip [1..] ([
+		Test13 (longstring,0),
+		Test3 [(map chr [8..16],99)],
+		Test4 [
+			Test4 $
+				Test2 "  teststr  " (Test1 2 '\'' ()) :
+					map (\ i -> Test1 (0-i) (chr i) ()) [0..255],
+			Test3 [("<DATATYPE name=\"Test\">",100)],
+			Test3 [(map chr [0..255],99)],
+			Test13 ("abc",3),
+			Test14 (3,4),
+			Test5,
+			Test4 [],
+			Test3 [("",0)],
+			Test6 (pi,pi,True),
+			Test6 (1.2345678e+37,1.2345678e-250,False),
+			Test7 [1..10] ],
+		Test8 $ Map.fromList [("abc",Test4 []),("def",Test5),("ghi",Test7 [1,2,3])],
+		Test4 [ Test3 [("first",2)], Test6 (1.23,1.23456,False) ],
+		Test9 (Just $ Test7 [1,2,3]) Nothing (Left $ Test1 7 'v' ()) (Right 8),
+		Test10 (Set.fromList [10,27,-1,1,3,5,7,9]),
+		Test11 (listArray (10,12) [3.3,4.4,5.5]),
+		Test12 (27 % 8) (1.23 :+ (-3.45)),
+		Test15 $ IntMap.fromList $ zip [-2..] [Test5, Test7 [-2,-1,0], Test13 ("test15",666) ],
+		Test16 [
+			TestSel1 4,
+			TestSel2 'f',
+			TestSel3 $ TestSel3 $ TestSel3 $ TestSel1 9999,
+			TestSel4 $ Test16 [TestSel1 5, TestSel4 $ Test5, TestSel5 ],
+			TestSel5 ],
+		Test17 "abc" 123 3.14,
+		Test1 12 'c' (),
+		Test18 13 'd' False "test18",
+		Test19 13 'd' False "test19" 14,
+		Test20 13 'd' False ("test20",'a') 15 2.71,
+		Test21 13 'd' False ("test21",'b',3) 15 2.71 (-1) 3.1 True 'e'
+		] :: [Test Integer Int Float])
+	rc <- case all id oks of
+		True -> putStrLn "All tests OK." >> return ExitSuccess
+		False -> putStrLn "Some tests failed!" >> return (ExitFailure 1)
+	exitWith rc
+ test/test.xml view
@@ -0,0 +1,10 @@+<?xml version="1.0" encoding="UTF-8"?>+<CONSTRUCTOR name="Test">+  <ARG-1 selector="str">abc</ARG-1>+  <ARG-2 selector="something">+    <PAIR>+      <FIRST>42</FIRST>+      <SECOND>z</SECOND>+    </PAIR>+  </ARG-2>+</CONSTRUCTOR>
+ tofromxml.cabal view
@@ -0,0 +1,33 @@+name:                tofromxml
+version:             0.1.0.0
+synopsis:            Reading/writing Haskell data from/to XML
+description:         A library for reading/writing Haskell data from/to XML. Only prerequisite is that the data type derives Generic.
+license:             BSD3
+license-file:        LICENSE
+author:              Robert Reitmeier
+maintainer:          Robert Reitmeier <concat ["public"," a t ","thinking-machines","dot",".net"]>
+stability:           provisional
+copyright:           (c) 2014 Robert Reitmeier
+category:            XML
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  test/Example.hs test/TestToFromXML.hs test/test.xml
+
+library
+  exposed-modules:   Text.XML.ToFromXML
+  other-extensions:  DeriveGeneric, DefaultSignatures, UndecidableInstances, ScopedTypeVariables, OverlappingInstances, TypeOperators, FlexibleContexts, FlexibleInstances
+  build-depends:     base >=4.7 && <4.8, bytestring >=0.10 && <0.11, hexpat-pickle >=0.6 && <0.7, hexpat >=0.20 && <0.21, containers >=0.5 && <0.6, array >=0.5 && <0.6
+  hs-source-dirs:    .
+  default-language:  Haskell2010
+  ghc-options:       -fcontext-stack=50
+
+Test-Suite test
+  type:              exitcode-stdio-1.0
+  main-is:           test/TestToFromXML.hs
+  build-depends:     base,tofromxml,array,containers,hexpat,hexpat-pickle,bytestring,filepath
+  default-language:  Haskell2010
+
+source-repository head
+  type:              git
+  location:          https://github.com/theoremprover/tofromxml.git
+  subdir:            work