diff --git a/Text/XML/Expat/Pickle.hs b/Text/XML/Expat/Pickle.hs
--- a/Text/XML/Expat/Pickle.hs
+++ b/Text/XML/Expat/Pickle.hs
@@ -1,186 +1,284 @@
-{-# LANGUAGE Rank2Types, FlexibleInstances, TypeSynonymInstances,
-             FlexibleContexts, MultiParamTypeClasses #-}
+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances,
+             UndecidableInstances, FunctionalDependencies #-}
 
--- | XML picklers based on hexpat which are almost source code compatible with
--- HXT.
+-- | @hexpat-pickle@ provides XML picklers that plug into the parse tree of the
+-- @hexpat@ package, providing XML serialization with excellent performance.
+-- They are source code similar to those of the HXT package. The concept and
+-- design was lifted entirely from HXT.
+--
+-- The API differences between @HXT@ and @hexpat-pickle@ are:
+--
+--  * 'PU' and 'XmlPickler' take one extra argument, indicating the part of the
+--        XML tree we are working with.
+--
+--  * 'xpElem' takes three arguments to HXT's two, because we treat attributes
+--        and child nodes separately, while HXT groups them together.
+--
+--  * HXT's list of pickler primitives is a little more complete.
+--
+-- The data type @'PU' t a@ represents both a pickler (converting Haskell data
+-- to XML) and an unpickler (XML to Haskell data), so your code only needs to be
+-- written once for both serialization and deserialization.  The 'PU' primitives, such
+-- as 'xpElem' for XML elements, may be composed into complex arrangements using
+-- 'xpPair' and other combinators.
+--
+-- The @t@ argument (absent in HXT) represents the part of the XML tree
+-- that this 'PU' works on. @t@ has /four/ possible values (assuming we are
+-- using the String type for our strings):
+--
+--  * @'Element' t String String => 'PU' t a@ /(for working with an XML element)/
+--
+--  * @'PU' ['Node' String String] a@ /(for working with lists of elements)/
+--
+--  * @'TextContent' t String => 'PU' t a@ /(for working with text content)/
+--
+--  * @'PU' [(String, String)] a@ /(for working with attributes)/
+--
+-- In the /list of elements/ and /attributes/ cases we use concrete types.
+-- However, 'Element' is implemented as a /type class/, because it behaves slightly
+-- differently in two situations: 1. The root node of a document, and 2. A child
+-- node (because in this situation it can search the list of nodes for a match).
+-- 'TextContent' is also a /type class/, because it behaves differently in
+-- 1. attribute values, and 2. text content of elements.
+--
+-- @hexpat-pickle@ can work with the following string types:
+--
+--  * String
+--
+--  * Data.ByteString
+--
+--  * Data.Text
+--
+-- and it is extensible to any other string type by 1. writing a @hexpat@ \"flavor\"
+-- for it, and making it an instance of 'GenericXMLString'.  We select the type for
+-- XML /tag/ and /text/ separately in our four \"tree part\" types as follows:
+--
+--  * @'Element' t /tag/ /text/ => 'PU' t a@
+--
+--  * @'PU' ['Node' /tag/ /text/] a@
+--
+--  * @'TextContent' t /text/ => 'PU' t a@
+--
+--  * @'PU' [(/tag/, /text/)]@
+--
+-- /tag/ may be a string type, or it may be a QName type defined in
+-- the 'Text.XML.Expat.Qualified' module.  (Or you can extend it any way you like.)
+--
+-- The type class 'XmlPickler' is used to extend a polymorphic 'xpickle' function
+-- to provide a pickler for a new type, in a similar way to 'Read' and 'Show'.
+--
+-- @hexpat-pickle@ abuses type classes to the extent that it requires a long
+-- list of GHC extensions. You will need most or all of these in your code:
+--
+-- @
+-- {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts,
+--              TypeSynonymInstances, UndecidableInstances #-}
+-- @
+--
+-- Here is a simple and complete example to get you started:
+--
+-- >  {-# LANGUAGE FlexibleContexts #-}
+-- >  
+-- >  import Text.XML.Expat.Tree
+-- >  import Text.XML.Expat.Pickle
+-- >  import Text.XML.Expat.Format
+-- >  import qualified Data.ByteString.Lazy as L
+-- >  
+-- >  -- Person name, age and description
+-- >  data Person = Person String Int String
+-- >  
+-- >  xpPerson :: Element t String String => PU t Person
+-- >  xpPerson =
+-- >      -- How to wrap and unwrap a Person
+-- >      xpWrap (\((name, age), descr) -> Person name age descr,
+-- >              \(Person name age descr) -> ((name, age), descr)) $
+-- >      xpElem "person"
+-- >          (xpPair
+-- >              (xpAttr "name" xpText0)
+-- >              (xpAttr "age" xpickle))
+-- >          xpText0
+-- >  
+-- >  people = [
+-- >      Person "Dave" 27 "A fat thin man with long short hair",
+-- >      Person "Jane" 21 "Lives in a white house with green windows"]
+-- >  
+-- >  main = do
+-- >      L.putStrLn $
+-- >          formatTree stringFlavor $
+-- >              pickleTree (xpElemNodes "people" $ xpList xpPerson) people
+--
+-- Program output:
+--
+-- > <?xml version="1.0" encoding="UTF-8"?>
+-- > <people><person name="Dave" age="27">A fat thin man with long short hair</person>
+-- > <person name="Jane" age="21">Lives in a white house with green windows</person></people>
+
 module Text.XML.Expat.Pickle (
-        PU_(PU),
-        Node,
-        PU,
-        pickleXML,
-        unpickleXML,
+        -- * Primary interface
+        PU(PU),
+        Node(..),
         XmlPickler(..),
-        pickleTree,
         unpickleTree,
+        pickleTree,
+        -- * Classes for abstracting parts of the tree
+        Element(..),
+        TextContent(..),
         -- * Pickler primitives
-        xpReadShow, -- nmrp3/drdozer
-        xpText0,
-        xpText,
+        xpUnit,
         xpElem,
+        xpElemAttrs,
+        xpElemNodes,
         xpAttr,
-        xpOption,
+        xpText0,
+        xpText,
+        xpReadShow,
+        -- * Pickler combinators
         xpPair,
         xpTriple,
         xp4Tuple,
         xp5Tuple,
+        xp6Tuple,
         xpList,
+        xpListMinLen,
+        -- * Pickler type conversion
         xpWrap,
         xpWrapMaybe,
-        xpWrapEither, -- nmrp3/drdozer
-        xpAllAttrs,
+        xpWrapEither,
+        -- * Pickler conditionals
+        xpOption,
         xpAlt,
-        xpUnit,
-        -- * Classes for abstracting parts of the tree
-        Stringable,
-        Nodeable,
-        Attrable
+        xpTryCatch,
+        xpThrow,
+        xpWithDefault,
+        -- * Pickler other
+        xpConst,
+        xpAttrs,
+        xpTree,
+        xpTrees,
+        -- * Abstraction of string types
+        GenericXMLString(..)
     ) where
 
+import Text.XML.Expat.IO (Encoding)
 import Text.XML.Expat.Tree
 import Text.XML.Expat.Format
+import Text.XML.Expat.Qualified
 import Data.Maybe
 import Data.Either
 import Data.List
 import Data.Char
+import Data.Monoid
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as BL
+import Data.ByteString.Internal (c2w, w2c)
+import qualified Data.Text as T
 import qualified Codec.Binary.UTF8.String as U8
 
 
--- | A two-way pickler/unpickler that pickles a ''t'' to type ''a''.  See
--- /Text.XML.Expat.Tree/ for the tree structure as used with ''t''.  ''t'' can be
--- /Node/, /[Node]/, /[(String,String)]/, /(String,String)/ or /String/.
---
--- A /PU_/ can be composed using the pickler primitives defined in this module.
-data PU_ t a = PU {
-        unpickleTree_ :: t -> Either String a,
-        pickleTree_   :: a -> t -> t
+-- | A two-way pickler/unpickler that pickles an arbitrary
+-- data type ''a'' to a part of an XML tree ''t''.
+-- A 'PU' can be composed using the pickler primitives defined in this module.
+data PU t a = PU {
+        -- | Convert a @t@ XML tree part into a Haskell value of type @a@, or give an
+        -- unpickling error message as @Left error@.
+        unpickleTree :: t -> Either String a,
+        -- | Convert a Haskell value of type @a@ to a @t@ XML tree part.
+        pickleTree   :: a -> t
     }
 
--- | In the most common case, where the part of the tree you're pickling/unpickling
--- is of type /Node/, you can use /PU/ and maintain source code compatibility with HXT. In
--- other cases you will need to use PU_, which will break compatibility.
-type PU a = PU_ Node a
-
 toByteString :: String -> B.ByteString
 toByteString = B.pack . map (fromIntegral . ord)
 
--- | Pickle a Haskell data structure to XML text. Outputs a strict ByteString.
-pickleXML :: Maybe Encoding -> PU_ Node a -> a -> B.ByteString
-pickleXML mEnc pu value = toByteString $ formatDoc mEnc $ pickleTree pu value
-
--- | Unpickle XML text to a Haskell data structure.  Takes a lazy ByteString.
-unpickleXML :: PU_ Node a -> Maybe Encoding -> BL.ByteString -> Either String a
-unpickleXML pu enc xml = do
-    case parse enc xml of
-        Just tree -> unpickleTree pu tree
-        Nothing   -> Left "XML parse failed"
-
 -- | We build the attributes and tags lists backwards, then reverse them afterwards
 -- for speed.
-reverseElement :: Node -> Node
+reverseElement :: Node tag text -> Node tag text
 reverseElement (Element eName eAttrs eChildren) = Element eName (reverse eAttrs) (reverse eChildren)
 reverseElement other = other
 
-pickleTree :: PU a -> a -> Node
-pickleTree pu value =
-    let Element _ _ elems = pickleTree_ pu value (Element "" [] [])
-    in  case elems of
-            elem:_ -> elem
-            _      -> error "No top-level element"
+-- | An abstraction for any string type you want to use as xml text (that is,
+-- attribute values or element text content). If you want to use a
+-- new string type with @hexpat-pickle@, you must make it an instance of
+-- 'GenericXMLString'.
+class GenericXMLString s where
+    gxNullString :: s -> Bool
+    gxToString :: s -> String
+    gxFromString :: String -> s
 
-unpickleTree :: PU a -> Node -> Either String a
-unpickleTree pu tree = unpickleTree_ pu (Element "" [] [tree])
+instance GenericXMLString String where
+    gxNullString = null
+    gxToString = id
+    gxFromString = id
 
--- | Takes one more argument than the HXT version of XmlPickler, which is the
--- type of the part of the tree, like /PU_/ does.
-class XmlPickler t a where
-    xpickle :: PU_ t a
+instance GenericXMLString B.ByteString where
+    gxNullString = B.null
+    gxToString = U8.decodeString . map w2c . B.unpack
+    gxFromString = B.pack . map c2w . U8.encodeString
 
-class Show t => Stringable t where
-    getString :: t -> String
-    putString :: String -> t -> t
+instance GenericXMLString T.Text where
+    gxNullString = T.null
+    gxToString = T.unpack
+    gxFromString = T.pack
 
-instance Stringable String where
-    getString = id
-    putString = \_ -> id
+-- | Define a generic pickler for converting a Haskell data of type @a@ to/from a
+-- @t@ tree part, analogous to 'Read' / 'Show'.
+class XmlPickler t a where
+    xpickle :: PU t a
 
-instance Stringable (String, String) where
-    getString (name, value) = value
-    putString value (name, _) = (name, value)
-    
-instance Stringable Node where
-    getString (Element _ _ eChildren) = getString eChildren
-    getString (Text str) = str
-    putString value (Element eName eAttrs eChildren) = Element eName eAttrs (putString value eChildren)
-    putString _ (Text _) = error "Can't put string into an existing text node"
+-- | Represents the two possible parts of an XML tree that can encode text
+-- content: 1. attribute values, and 2. text content of elements.
+class (Monoid text, GenericXMLString text) => TextContent t text | t -> text where
+    getString :: t -> text
+    putString :: text -> t
 
--- | Tree parts that can be treated as strings.
-instance Stringable [Node] where
-    getString = concatMap ex
-        where
-            ex (Text str) = str
-            ex _ = []
-    putString "" nodes = nodes
-    putString txt nodes = Text txt:nodes
+instance TextContent String String where
+    getString = id
+    putString = id
 
--- | Tree parts that can be treated as nodes.
-class Show t => Nodeable t where
-    getNodes    :: t -> [Node]
-    getSubnodes :: t -> [Node]
-    addChild    :: Node -> t -> t
+instance TextContent B.ByteString B.ByteString where
+    getString = id
+    putString = id
 
-instance Nodeable [Node] where
-    getNodes = id
-    getSubnodes = id
-    addChild = (:)
+instance TextContent T.Text T.Text where
+    getString = id
+    putString = id
 
-instance Nodeable Node where
-    getNodes elt@(Element _ _ _) = [elt]
-    getNodes _                   = error "No nodes to be found inside tag text"
-    getSubnodes (Element _ _ children) = children
-    getSubnodes _                = error "No subnodes to be found inside tag text"
-    addChild value elt@(Element name attrs children) = Element name attrs (value `addChild` children)
-    addChild _ _               = error "Can't append node to text tag"
+instance (GenericXMLString text, Monoid text) => TextContent [Node tag text] text where
+    getString = mconcat . map extract
+        where
+            extract (Element _ _ children) = getString children
+            extract (Text txt) = txt
+    putString txt | gxNullString txt = []
+    putString txt = [Text txt]
 
--- | Tree parts that can be treated as attributes.
-class Show t => Attrable t where
-    getAttrs    :: t -> [(String,String)]
-    putAttrs    :: [(String,String)] -> t -> t
+-- | Represents an XML element in a general way.
+class Show t => Element t tag text | t -> tag, t -> text where
+    getElements      :: t -> [Node tag text]
+    putElement       :: Node tag text -> t
 
-instance Attrable Node where
-    getAttrs (Element _ attrs _) = attrs
-    getAttrs _                   = error "No attributes to be found inside tag text"
-    putAttrs attrs (Element name _ children) = Element name attrs children
-    putAttrs _ _                 = error "Can't put attributes into tag text"
-    
-instance Attrable [(String,String)] where
-    getAttrs = id
-    putAttrs attrs _ = attrs
+instance (Show tag, Show text) => Element [Node tag text] tag text where
+    getElements = id
+    putElement t = [t]
 
-getAttributes (Element _ eAttrs _) = eAttrs
-getAttributes (Text _)             = error "No attributes to be found inside tag text"
-setAttribute (name, value) (Element eName eAttrs eChildren) =
-        Element eName eAttrs' eChildren
-    where
-        eAttrs' = (name, value):eAttrs
-setAttribute _ (Text _)            = error "No attributes to be found inside tag text"
+instance (Show tag, Show text) => Element (Node tag text) tag text where
+    getElements elt@(Element _ _ _) = [elt]
+    getElements _                   = error "No nodes to be found inside tag text"
+    putElement                      = id
 
--- | Convert XML text \<-\> String. Handles empty strings.
-xpText0 :: Stringable t => PU_ t String
+-- | Convert XML text content \<-\> String. Handles empty strings.
+xpText0 :: TextContent t text => PU t text
 xpText0 = PU {
-        unpickleTree_ = Right . U8.decodeString . getString,
-        pickleTree_  = putString . U8.encodeString
+        unpickleTree = Right . getString,
+        pickleTree  = putString
     }
 
--- | Convert XML text \<-\> String. Empty strings result in unpickle failure.
-xpText :: Stringable t => PU_ t String
+-- | Convert XML text content \<-\> String. Empty strings result in unpickle failure (Be warned!).
+xpText :: TextContent t text => PU t text
 xpText = PU {
-        unpickleTree_ = \t ->
+        unpickleTree = \t ->
             case getString t of
-                "" -> Left "empty text"
-                txt -> Right (U8.decodeString txt),
-        pickleTree_  = putString . U8.encodeString
+                txt | gxNullString txt -> Left "empty text"
+                txt -> Right txt,
+        pickleTree = putString
     }
 
 maybeRead :: Read a => String -> Maybe a
@@ -188,209 +286,333 @@
     [(x, "")] -> Just x
     _         -> Nothing
 
-instance Stringable s => XmlPickler s Int where
+instance TextContent s text => XmlPickler s Int where
     xpickle = xpReadShow
 
-instance Stringable s => XmlPickler s Integer where
+instance TextContent s text => XmlPickler s Integer where
     xpickle = xpReadShow
 
--- | Convert an XML string \<-\> a type that implements Read and Show.
-xpReadShow :: (Stringable s, Read n, Show n) => PU_ s n
+-- | Convert XML text content \<-\> any type that implements 'Read' and 'Show'.
+xpReadShow :: (TextContent s text, Read n, Show n) => PU s n
 xpReadShow = PU {
-            unpickleTree_ = \t ->
-                case maybeRead (getString t) of
+            unpickleTree = \t ->
+                let txt = gxToString $ getString t
+                in  case maybeRead txt of
                     Just val -> Right val
-                    Nothing  -> Left "bad numeric value",
-            pickleTree_ = \n -> putString (show n)
+                    Nothing  -> Left $ "failed to read text: "++txt,
+            pickleTree = \n -> putString (gxFromString $ show n)
         }
 
-instance XmlPickler Node a => XmlPickler Node [a] where
+instance (XmlPickler (Node tag text) a, Show tag, Show text) =>
+         XmlPickler [Node tag text] [a] where
     xpickle = xpList xpickle
 
--- | Create/parse an XML element of the specified name.  Fails if an element of
--- this name can't be found at this point in the tree.  This implementation unpickles
--- elements of different names in any order, while HXT's xpElem will fail if the
--- XML order doesn't match the Haskell code.
-xpElem :: Nodeable t => String -> PU_ Node a -> PU_ t a
-xpElem name pu = PU {
-        unpickleTree_ = \t ->
-            let nodes = getSubnodes t
-                doElem elt@(Element eName _ _) | eName == name =
-                    Just $ unpickleTree_ pu elt
+-- | Pickle @(a,b)@ to/from an XML element of the specified name, where @a@
+-- is passed to a specified pickler for attributes and @b@ to a pickler for child nodes.
+-- Unpickle fails if an element of this name can't be found at this point in the tree.
+--
+-- This implementation differs from HXT in that it unpickles elements of different
+-- names in any order, while HXT's xpElem will fail if the XML order doesn't match
+-- the Haskell code.
+--
+-- It also differs from HXT in that it takes two pickler arguments, one for attributes
+-- and one for child nodes. When migrating from HXT, often you can substitute just
+-- 'xpElemAttrs' or 'xpElemNodes' for HXT's 'xpElem', but where your element has both
+-- attributes and child nodes, you must split your data into a 2-tuple with 'xpWrap',
+-- and separate the child picklers accordingly.
+xpElem :: (Eq tag, Show tag, Element t tag text) =>
+          tag                  -- ^ Element name
+       -> PU [(tag, text)] a   -- ^ Pickler for attributes
+       -> PU [Node tag text] b -- ^ Pickler for child nodes: accepts @Element t tag text => 'PU' t a@
+       -> PU t (a,b)
+xpElem name puAttrs puChildren = PU {
+        unpickleTree = \t ->
+            let doElem elt@(Element eName attrs children) | eName == name =
+                    Just $ (unpickleTree puAttrs attrs, unpickleTree puChildren children)
                 doElem _ = Nothing
-                mVals   = map doElem nodes
-            in  case catMaybes mVals of
-                    Right val:_ -> Right val
-                    Left err:_  -> Left $ "in <"++name++">, "++err
-                    []    -> Left $ "can't find element <"++name++">",
-        pickleTree_ = \value nodes ->
-            reverseElement (pickleTree_ pu value (Element name [] []))
-                `addChild` nodes
+                mChildren = map doElem (getElements t)
+            in  case catMaybes mChildren of
+                    []    -> Left $ "can't find element <"++show name++">"
+                    (result:_) -> case result of
+                        (Right attrs, Right children) -> Right (attrs, children)
+                        (Left err, _) -> Left $ "in attributes of <"++show name++">, "++err
+                        (_, Left err) -> Left $ "in <"++show name++">, "++err,
+        pickleTree = \(a,b) ->
+            putElement $ Element name (pickleTree puAttrs a) (pickleTree puChildren b)
     }
 
+-- | A helper variant of xpElem for elements that contain attributes but no child tags.
+xpElemAttrs :: (Eq tag, Show tag, Element t tag text) =>
+               tag                 -- ^ Element name
+            -> PU [(tag, text)] a  -- ^ Pickler for attributes
+            -> PU t a
+xpElemAttrs name puAttrs = xpWrap (fst,\a -> (a,())) $ xpElem name puAttrs xpUnit
+
+-- | A helper variant of xpElem for elements that contain child nodes but no attributes.
+xpElemNodes :: (Eq tag, Show tag, Element t tag text) =>
+               tag                   -- ^ Element name
+            -> PU [Node tag text] a  -- ^ Pickler for child nodes: accepts @Element t tag text => 'PU' t a@
+            -> PU t a
+xpElemNodes name puChildren = xpWrap (snd,\a -> ((),a)) $ xpElem name xpUnit puChildren
+
 -- | Create/parse an XML attribute of the specified name.  Fails if the attribute
 -- can't be found at this point in the tree.
-xpAttr :: String -> PU_ (String, String) a -> PU_ Node a
+xpAttr :: (Eq tag, Show tag) => tag -> PU text a -> PU [(tag,text)] a
 xpAttr name pu = PU {
-        unpickleTree_ = \t ->
-            let attrs = getAttributes t
-                doAttr attr@(aName, value) | aName == name =
-                    case unpickleTree_ pu attr of
+        unpickleTree = \attrs ->
+            let doAttr attr@(aName, value) | aName == name =
+                    case unpickleTree pu value of
                         Right val -> Just val
                         Left _    -> Nothing
                 doAttr _ = Nothing
-                mVals = map doAttr attrs
-            in  case catMaybes mVals of
+                mAttrs = map doAttr attrs
+            in  case catMaybes mAttrs of
                     val:_ -> Right val
-                    []    -> Left $ "can't find attribute '"++name++"'",
-        pickleTree_ = \value attrs ->
-            pickleTree_ pu value (name, "") `setAttribute` attrs
+                    []    -> Left $ "can't find attribute '"++show name++"'",
+        pickleTree = \value -> [(name, pickleTree pu value)]
     }
 
--- | Convert XML text <-> a Maybe type. During unpickling, Nothing is returned
--- if there's a failure during the unpickling of the first argument.
-xpOption :: PU_ t a -> PU_ t (Maybe a)
+-- | Convert XML text \<-\> a Maybe type. During unpickling, Nothing is returned
+-- if there's a failure during the unpickling of the first argument.  A typical
+-- example is:
+--
+-- > xpElemAttrs "score" $ xpOption $ xpAttr "value" xpickle
+--
+-- in which @Just 5@ would be encoded as @\<score value=\"5\"\/\>@ and @Nothing@ would be
+-- encoded as @\<score\/\>@.
+xpOption :: PU [t] a -> PU [t] (Maybe a)
 xpOption pu = PU {
-        unpickleTree_ = \t ->
-            case unpickleTree_ pu t of
+        unpickleTree = \t ->
+            case unpickleTree pu t of
                 Right val -> Right (Just val)
                 Left _    -> Right Nothing,
-        pickleTree_ = \mValue t ->
+        pickleTree = \mValue ->
             case mValue of
-                Just value -> pickleTree_ pu value t
-                Nothing    -> t
+                Just value -> pickleTree pu value
+                Nothing    -> []
     }
 
 -- | Convert XML text \<-\> a 2-tuple using the two arguments.
-xpPair :: PU_ t a -> PU_ t b -> PU_ t (a,b)
+xpPair :: PU [t] a -> PU [t] b -> PU [t] (a,b)
 xpPair pua pub = PU {
-        unpickleTree_ = \t ->
-            case (unpickleTree_ pua t, unpickleTree_ pub t) of
+        unpickleTree = \t ->
+            case (unpickleTree pua t, unpickleTree pub t) of
                 (Right a, Right b) -> Right (a,b)
-                (Left err, _) -> Left err
-                (_, Left err) -> Left err,
-        pickleTree_ = \(a, b) t ->
-            pickleTree_ pub b $ pickleTree_ pua a t
+                (Left err, _) -> Left $ "in 1st of pair, "++err
+                (_, Left err) -> Left $ "in 2nd of pair, "++err,
+        pickleTree = \(a, b) ->
+            pickleTree pua a ++
+            pickleTree pub b
     }
 
 -- | Convert XML text \<-\> a 3-tuple using the three arguments.
-xpTriple :: PU_ t a -> PU_ t b -> PU_ t c -> PU_ t (a,b,c)
+xpTriple :: PU [t] a -> PU [t] b -> PU [t] c -> PU [t] (a,b,c)
 xpTriple pua pub puc = PU {
-        unpickleTree_ = \t ->
-            case (unpickleTree_ pua t, unpickleTree_ pub t, unpickleTree_ puc t) of
+        unpickleTree = \t ->
+            case (unpickleTree pua t, unpickleTree pub t, unpickleTree puc t) of
                 (Right a, Right b, Right c) -> Right (a,b,c)
-                (Left err, _, _) -> Left err
-                (_, Left err, _) -> Left err
-                (_, _, Left err) -> Left err,
-        pickleTree_ = \(a, b, c) t ->
-            pickleTree_ puc c $ pickleTree_ pub b $ pickleTree_ pua a t
+                (Left err, _, _) -> Left $ "in 1st of triple, "++err
+                (_, Left err, _) -> Left $ "in 2nd of triple, "++err
+                (_, _, Left err) -> Left $ "in 3rd of triple, "++err,
+        pickleTree = \(a, b, c) ->
+            pickleTree pua a ++
+            pickleTree pub b ++
+            pickleTree puc c
     }
 
 -- | Convert XML text \<-\> a 4-tuple using the four arguments.
-xp4Tuple :: PU_ t a -> PU_ t b -> PU_ t c -> PU_ t d -> PU_ t (a,b,c,d)
+xp4Tuple :: PU [t] a -> PU [t] b -> PU [t] c -> PU [t] d -> PU [t] (a,b,c,d)
 xp4Tuple pua pub puc pud = PU {
-        unpickleTree_ = \t ->
-            case (unpickleTree_ pua t, unpickleTree_ pub t, unpickleTree_ puc t, unpickleTree_ pud t) of
+        unpickleTree = \t ->
+            case (unpickleTree pua t, unpickleTree pub t, unpickleTree puc t,
+                  unpickleTree pud t) of
                 (Right a, Right b, Right c, Right d) -> Right (a,b,c,d)
-                (Left err, _, _, _) -> Left err
-                (_, Left err, _, _) -> Left err
-                (_, _, Left err, _) -> Left err
-                (_, _, _, Left err) -> Left err,
-        pickleTree_ = \(a, b, c, d) t ->
-            pickleTree_ pud d $ pickleTree_ puc c $ pickleTree_ pub b $ pickleTree_ pua a t
+                (Left err, _, _, _) -> Left $ "in 1st of 4-tuple, "++err
+                (_, Left err, _, _) -> Left $ "in 2nd of 4-tuple, "++err
+                (_, _, Left err, _) -> Left $ "in 3rd of 4-tuple, "++err
+                (_, _, _, Left err) -> Left $ "in 4th of 4-tuple, "++err,
+        pickleTree = \(a, b, c, d) ->
+            pickleTree pua a ++
+            pickleTree pub b ++
+            pickleTree puc c ++
+            pickleTree pud d
     }
 
 -- | Convert XML text \<-\> a 5-tuple using the five arguments.
-xp5Tuple :: PU_ t a -> PU_ t b -> PU_ t c -> PU_ t d -> PU_ t e -> PU_ t (a,b,c,d, e)
+xp5Tuple :: PU [t] a -> PU [t] b -> PU [t] c -> PU [t] d -> PU [t] e -> PU [t] (a,b,c,d,e)
 xp5Tuple pua pub puc pud pue = PU {
-        unpickleTree_ = \t ->
-            case (unpickleTree_ pua t, unpickleTree_ pub t, unpickleTree_ puc t, unpickleTree_ pud t, unpickleTree_ pue t) of
+        unpickleTree = \t ->
+            case (unpickleTree pua t, unpickleTree pub t, unpickleTree puc t,
+                  unpickleTree pud t, unpickleTree pue t) of
                 (Right a, Right b, Right c, Right d, Right e) -> Right (a,b,c,d,e)
-                (Left err, _, _, _, _) -> Left err
-                (_, Left err, _, _, _) -> Left err
-                (_, _, Left err, _, _) -> Left err
-                (_, _, _, Left err, _) -> Left err
-                (_, _, _, _, Left err) -> Left err,
-        pickleTree_ = \(a, b, c, d, e) t ->
-            pickleTree_ pue e $ pickleTree_ pud d $ pickleTree_ puc c $ pickleTree_ pub b $ pickleTree_ pua a t
+                (Left err, _, _, _, _) -> Left $ "in 1st of 5-tuple, "++err
+                (_, Left err, _, _, _) -> Left $ "in 2nd of 5-tuple, "++err
+                (_, _, Left err, _, _) -> Left $ "in 3rd of 5-tuple, "++err
+                (_, _, _, Left err, _) -> Left $ "in 4th of 5-tuple, "++err
+                (_, _, _, _, Left err) -> Left $ "in 5th of 5-tuple, "++err,
+        pickleTree = \(a, b, c, d, e) ->
+            pickleTree pua a ++
+            pickleTree pub b ++
+            pickleTree puc c ++
+            pickleTree pud d ++
+            pickleTree pue e
     }
 
+-- | Convert XML text \<-\> a 6-tuple using the six arguments.
+xp6Tuple :: PU [t] a -> PU [t] b -> PU [t] c -> PU [t] d -> PU [t] e -> PU [t] f -> PU [t] (a,b,c,d,e,f)
+xp6Tuple pua pub puc pud pue puf = PU {
+        unpickleTree = \t ->
+            case (unpickleTree pua t, unpickleTree pub t, unpickleTree puc t,
+                  unpickleTree pud t, unpickleTree pue t, unpickleTree puf t) of
+                (Right a, Right b, Right c, Right d, Right e, Right f) -> Right (a,b,c,d,e,f)
+                (Left err, _, _, _, _, _) -> Left $ "in 1st of 6-tuple, "++err
+                (_, Left err, _, _, _, _) -> Left $ "in 2nd of 6-tuple, "++err
+                (_, _, Left err, _, _, _) -> Left $ "in 3rd of 6-tuple, "++err
+                (_, _, _, Left err, _, _) -> Left $ "in 4th of 6-tuple, "++err
+                (_, _, _, _, Left err, _) -> Left $ "in 5th of 6-tuple, "++err
+                (_, _, _, _, _, Left err) -> Left $ "in 6th of 6-tuple, "++err,
+        pickleTree = \(a, b, c, d, e, f) ->
+            pickleTree pua a ++
+            pickleTree pub b ++
+            pickleTree puc c ++
+            pickleTree pud d ++
+            pickleTree pue e ++
+            pickleTree puf f
+    }
+
 -- | Convert XML text \<-\> a list of elements. During unpickling, failure of the
 -- argument unpickler is the end-of-list condition (and it isn't a failure).
-xpList :: PU_ Node a -> PU_ Node [a]
+xpList :: (Show tag, Show text) => PU (Node tag text) a -> PU [Node tag text] [a]
 xpList pu = PU {
-        unpickleTree_ = \t ->
-            let nodes = getSubnodes t
-                munge [] out = out
-                munge elts@(Element _ _ _:rest) out =
-                    case unpickleTree_ pu (Element "" [] [head elts]) of
-                        Right val -> munge rest (val:out)
-                        Left _    -> out
-                munge (Text _:rest) out = munge rest out
-            in  Right $ reverse $ munge nodes [],
-        pickleTree_ = \list t ->
-            foldr (\elt t -> pickleTree_ pu elt t) t (reverse list)
+        unpickleTree = \nodes ->
+            let munge [] = []
+                munge (elt@(Element _ _ _):rem) =
+                    case unpickleTree pu elt of
+                        Right val -> val:munge rem
+                        Left _    -> []
+                munge (_:rem) = munge rem  -- ignore text nodes
+            in  Right $ munge nodes,
+        pickleTree = map (pickleTree pu)
     }
 
--- | Apply a lens to convert the type of your data structure into something that
--- the pickler primitives can handle (such as tuples).
-xpWrap :: (a -> b, b -> a) -> PU_ t a -> PU_ t b
+-- | Like xpList, but only succeed during deserialization it at least a minimum number of elements are unpickled.
+xpListMinLen :: (Show tag, Show text) => Int -> PU (Node tag text) a -> PU [Node tag text] [a]
+xpListMinLen ml = xpWrapEither (testLength, id) . xpList
+  where
+    testLength as | length as < ml = Left $ "Expecting at least " ++ show ml ++ " elements"
+    testLength as = Right as
+
+-- | Apply a lens to convert the type of your data structure to/from types that
+-- the pickler primitives can handle, with the /unpickle/ case first.
+-- Mostly this means the tuples used by 'xpPair' and friends. A typical example is:
+--
+-- > xpWrap (\(name, address) -> Person name address,
+-- >         \(Person name address) -> (name, address)) $ ...
+xpWrap :: (a -> b, b -> a) -> PU t a -> PU t b
 xpWrap (a2b, b2a) pua = PU {
-        unpickleTree_ = \t -> case unpickleTree_ pua t of
+        unpickleTree = \t -> case unpickleTree pua t of
             Right val -> Right (a2b val)
             Left err  -> Left err,
-        pickleTree_ = \value t -> pickleTree_ pua (b2a value) t
+        pickleTree = \value -> pickleTree pua (b2a value)
     }
 
--- Like xpWrap, but removes Just (and treats Nothing as a failure) during unpickling.
-xpWrapMaybe :: (a -> Maybe b, b -> a) -> PU_ t a -> PU_ t b
+-- | Like xpWrap, but strips Just (and treats Nothing as a failure) during unpickling.
+xpWrapMaybe :: (a -> Maybe b, b -> a) -> PU t a -> PU t b
 xpWrapMaybe (a2b, b2a) pua = PU {
-        unpickleTree_ = \t -> case unpickleTree_ pua t of
+        unpickleTree = \t -> case unpickleTree pua t of
             Right val ->
                 case a2b val of
                     Just val' -> Right val'
                     Nothing   -> Left "xpWrapMaybe can't encode Nothing value"
             Left err  -> Left err,
-        pickleTree_ = \value t -> pickleTree_ pua (b2a value) t
+        pickleTree = \value -> pickleTree pua (b2a value)
     }
 
--- Like xpWrap, except it removes Right (and treats Left as a failure) during unpickling.
-xpWrapEither :: (a -> Either String b, b -> a) -> PU_ t a -> PU_ t b
+-- | Like xpWrap, except it strips Right (and treats Left as a failure) during unpickling.
+xpWrapEither :: (a -> Either String b, b -> a) -> PU t a -> PU t b
 xpWrapEither (a2b, b2a) pua = PU {
-        unpickleTree_ = \t -> case unpickleTree_ pua t of
+        unpickleTree = \t -> case unpickleTree pua t of
             Right val -> a2b val
-            Left err  -> Left err,
-        pickleTree_ = \value t -> pickleTree_ pua (b2a value) t
-    }
-
--- Convert an attribute list in the XML tree into [(String, String)]. (Does not
--- exist in HXT.)
-xpAllAttrs :: Attrable t => PU_ t [(String, String)]
-xpAllAttrs = PU {
-        unpickleTree_ = \t -> Right (getAttrs t),
-        pickleTree_  = putAttrs
+            Left err  -> Left $ "xpWrapEither failed: "++err,
+        pickleTree = \value -> pickleTree pua (b2a value)
     }
 
--- | Allow alternative picklers. Selector function is used during pickling, but
--- unpickling is done by trying each list element in order until one succeeds.
-xpAlt :: (a -> Int)  -- ^ Selector
-      -> [PU_ t a]
-      -> PU_ t a
+-- | Execute one of a list of picklers. The /selector function/ is used during pickling, and
+-- the integer returned is taken as a 0-based index to select a pickler from /pickler options/.
+-- Unpickling is done by trying each list element in order until one succeeds
+-- (the /selector/ is not used).
+--
+-- This is typically used to handle each constructor of a data type. However, it
+-- can be used wherever multiple serialization strategies apply to a single type.
+xpAlt :: (a -> Int)  -- ^ selector function
+      -> [PU t a]    -- ^ list of picklers
+      -> PU t a
 xpAlt selector picklers = PU {
-        unpickleTree_ = \t ->
+        unpickleTree = \t ->
             let tryAll [] = Left "all xpAlt unpickles failed"
                 tryAll (x:xs) =
-                    case unpickleTree_ x t of
+                    case unpickleTree x t of
                         Right val -> Right val
                         Left err  -> tryAll xs
             in  tryAll picklers,
-        pickleTree_ = \value t -> pickleTree_ (picklers !! (selector value)) value t
+        pickleTree = \value -> pickleTree (picklers !! (selector value)) value
     }
 
 -- | Convert nothing \<-\> (). Does not output or consume any XML text. 
-xpUnit :: PU_ t ()
-xpUnit = PU {
-        unpickleTree_ = \t -> Right (),
-        pickleTree_ = \_ t -> t
+xpUnit :: PU [t] ()
+xpUnit = xpConst ()
+
+-- | Convert nothing \<-\> constant value. Does not output or consume any XML text.
+xpConst :: a -> PU [t] a
+xpConst a = PU
+  { unpickleTree = const $ Right a
+  , pickleTree = const []
+  }
+
+-- | Pickler that during pickling always uses the first pickler, and during
+-- unpickling tries the first, and on failure then tries the second.
+xpTryCatch :: PU t a -> PU t a -> PU t a
+xpTryCatch pu1 pu2 = PU
+  { unpickleTree = \t -> case unpickleTree pu1 t of
+             Right val1 -> Right val1
+             Left  err1 -> case unpickleTree pu2 t of
+                 Right val2 -> Right val2
+                 Left  err2 -> Left $ "Both xpTryCatch picklers failed: <" ++ err1 ++ "> <" ++ err2 ++ ">"
+  , pickleTree = pickleTree pu1
+  }
+
+-- | No output when pickling, always generates an error with the specified message on unpickling.
+xpThrow :: String    -- ^ Error message
+        -> PU [t] a
+xpThrow msg = PU
+  { unpickleTree = \t -> Left msg
+  , pickleTree = const []
+  }
+
+-- | Attempt to use a pickler. On failure, return a default value.
+xpWithDefault :: a -> PU [t] a -> PU [t] a
+xpWithDefault a pa = xpTryCatch pa (xpConst a)
+
+-- | Insert/extract an attribute list literally in the xml stream.
+xpAttrs :: PU [(tag, text)] [(tag, text)]
+xpAttrs = PU {
+        unpickleTree = Right,
+        pickleTree  = id
+    }
+
+-- | Insert/extract a tree node literally in the xml stream.
+xpTree :: Element t tag text => PU t (Node tag text)
+xpTree = PU {
+        unpickleTree = \t -> case getElements t of
+            [elt]     -> Right elt
+            otherwise -> Left "xpLiteral expects a single node",
+        pickleTree = putElement
+    }
+
+-- | Insert/extract a list of tree nodes literally in the xml stream.
+xpTrees :: PU [Node tag text] [Node tag text]
+xpTrees = PU {
+        unpickleTree = Right,
+        pickleTree = id
     }
 
diff --git a/hexpat-pickle.cabal b/hexpat-pickle.cabal
--- a/hexpat-pickle.cabal
+++ b/hexpat-pickle.cabal
@@ -1,32 +1,34 @@
 Cabal-Version: >= 1.2
 Name: hexpat-pickle
-Version: 0.1
-Synopsis: XML picklers based on hexpat, almost source code compatible with HXT
+Version: 0.2
+Synopsis: XML picklers based on hexpat, source-code-similar to those of the HXT package
 Description:
   A library of combinators that allows Haskell data structures to be pickled
-  to/from the Tree datatype defined in the hexpat package. It is almost source
-  code compatible with the pickling functionality of the HXT package, to allow
-  you to switch easily between the two implementations. This implementation is
-  simpler and more limited than HXT, but considerably faster.
-  This package does not depend on HXT.
+  (serialized) to/from XML via the Tree datatype defined in the hexpat package.
+  It is source-code-similar to the pickling functionality of the HXT package,
+  to ease the transition between the two libraries. This implementation is
+  faster but less complete than HXT. This package does not depend on HXT.
   .
+  Note that hexpat-pickle is still under development, so it is fairly likely
+  that there will be some API changes coming.
+  .
   DARCS repository:
   <http://code.haskell.org/hexpat-pickle/>
-  .
-  Information/tutorial about the picklers found in the HXT package:
-  <http://www.haskell.org/haskellwiki/HXT/Conversion_of_Haskell_data_from/to_XML>
 Category: XML
 License: BSD3
 License-File: LICENSE
-Author: Stephen Blackheath, Matthew Pocock
+Author: Stephen Blackheath (blackh), Matthew Pocock (drdozer)
 Maintainer: http://blacksapphire.com/antispam/
-Copyright: (c) 2009 Stephen Blackheath
+Copyright:
+  (c) 2009 Stephen Blackheath <http://blacksapphire.com/antispam/>,
+  (c) 2009 Matthew Pocock <matthew.pocock@ncl.ac.uk>
 Homepage: http://code.haskell.org/hexpat-pickle/
-Extra-Source-Files: test.hs
+Extra-Source-Files: test/test.hs, test/example.hs
 Build-Type: Simple
 Stability: alpha
 
 Library
-  Build-Depends: base, hexpat >= 0.3, utf8-string >= 0.3.3, bytestring >= 0.9
+  Build-Depends: base, hexpat >= 0.4, utf8-string >= 0.3.3, bytestring >= 0.9,
+      text >= 0.1
   Exposed-Modules: Text.XML.Expat.Pickle
 
diff --git a/test.hs b/test.hs
deleted file mode 100644
--- a/test.hs
+++ /dev/null
@@ -1,164 +0,0 @@
-{-# LANGUAGE Rank2Types, FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses #-}
-
-import qualified Text.XML.Expat.IO as EIO
-import Text.XML.Expat.Tree as ETree
-import Text.XML.Expat.Pickle
-import Text.XML.Expat.Format
-import Data.Tree
-import qualified Data.ByteString.Lazy as B
-import qualified Data.ByteString.Internal as B (w2c)
-import Control.Monad
-import Data.Maybe
-import Data.Either
-import Data.List
-import Data.Char
-import Debug.Trace
-import Data.Time.Clock
-import Numeric
-import System.IO
-
-class Key k where
-    keyValue :: k -> String
-
-class Key k => LanguageKey k where
-    isoOf :: k -> String
-    makeLanguageKey :: String -> k
-
-data MnemonicKey = MnemonicKey String
-    deriving Show
-
-instance Key MnemonicKey where
-    keyValue (MnemonicKey str) = str
-
-data LanguageKey k => MultiText k = MultiText {
-        lang         :: k,
-        languageText :: String,
-        timestamp    :: Integer
-    }
-    deriving (Eq, Show)
-
-instance LanguageKey k => XmlPickler Node (MultiText k) where
-    xpickle = xpMultiText "text"
-
-data SiteLanguageKey = SiteLanguageKey String
-    deriving Show
-
-instance Key SiteLanguageKey where
-    keyValue (SiteLanguageKey a) = a
-
-instance LanguageKey SiteLanguageKey where
-    isoOf = keyValue
-    makeLanguageKey str = SiteLanguageKey str
-
-maybeRead :: Read a => String -> Maybe a
-maybeRead s = case reads s of
-    [(x, "")] -> Just x
-    _         -> Nothing
-
-xpMultiText :: LanguageKey k => String -> PU (MultiText k)
-xpMultiText tagName =
-    xpWrap (
-        (\(lan, tex1, tim, tex2) -> MultiText
-            (makeLanguageKey lan)
-            (if tex2 /= "" then tex2 else fromMaybe "" tex1)
-            (truncate $ fromMaybe 0 ((maybeRead (fromMaybe "0" tim))::Maybe Float))),
-        (\(MultiText lan tex tim) -> (isoOf lan, Nothing, Just $ show tim, tex))
-    ) $
-    xpElem tagName $
-    xp4Tuple
-        (xpAttr "lang" xpText0)
-        (xpOption $ xpAttr "text" xpText0)
-        (xpOption $ xpAttr "time" xpText0)
-        xpText0
-
-data LanguageKey k => MultiLanguage k = MultiLanguage {
-        texts :: [MultiText k]
-    }
-    deriving (Eq, Show)
-
-nullMultiLanguage :: LanguageKey k => MultiLanguage k
-nullMultiLanguage = MultiLanguage []
-
-instance XmlPickler Node (MultiLanguage SiteLanguageKey) where
-    xpickle = xpMultiLanguage "text"
-
-xpMultiLanguage :: LanguageKey k => String -> PU (MultiLanguage k)
-xpMultiLanguage childTagName =
-    xpWrap (
-        (\ts -> MultiLanguage ts),
-        (\(MultiLanguage ts) -> ts)
-    ) $
-    xpList (xpMultiText childTagName)
-
-data Mnemonic = Mnemonic {
-        mnemonicKey   :: MnemonicKey,
-        category      :: String,
-        comment       :: String,
-        multiLanguage :: MultiLanguage SiteLanguageKey,
-        linkTo        :: Maybe MnemonicKey
-    }
-    deriving (Show)
-
-instance XmlPickler Node Mnemonic where
-    xpickle = xpMnemonic
-
-xpMnemonic :: PU Mnemonic
-xpMnemonic = xpElem "mnemonic" $ xpMnemonicAttrs
-
-xpMnemonicAttrs :: PU Mnemonic
-xpMnemonicAttrs =
-    xpWrap (
-        (\(mne, cat, com, tex, lin) ->
-            Mnemonic (MnemonicKey mne) cat (fromMaybe "" com) tex
-                (liftM (MnemonicKey . ("category."++)) lin)),
-        (\(Mnemonic (MnemonicKey mne) cat com tex lin) ->
-            (mne, cat, if com == "" then Nothing else Just com, tex,
-                liftM ((fromMaybe "" . stripPrefix "category.") . keyValue) lin))
-    ) $
-    xp5Tuple
-        (xpAttr "name" xpText0)
-        (xpAttr "category" xpText0)
-        (xpOption $ xpAttr "comment" $ xpText0)
-        (xpMultiLanguage "text")
-        (xpOption $ xpAttr "link" $ xpText)
-
-main_eio doc = do
-  parser <- EIO.newParser Nothing
-  EIO.setStartElementHandler parser startElement
-  EIO.parse parser doc -- True
-  putStrLn "ok"
-  where
-  startElement name attrs = putStrLn $ show name ++ " " ++ show attrs
-
-main_tree doc = do
-  start <- getCurrentTime
-  let mTree = ETree.parse Nothing doc
-  --putStrLn $ show mTree
-  case mTree of
-      Just tree -> do
-          let pickler = xpElem "mnemonics" $ xpList xpMnemonic
-          let eMnems = unpickleTree pickler tree
-          case eMnems of
-              Right mnems -> do
-                  --putStrLn $ show mnems
-                  let pickled = pickleTree pickler mnems
-                      out = formatDocS (Just UTF8) pickled "\n"
-                  --putStrLn $ show pickled
-                  --putStrLn $ out
-                  let xml =  map B.w2c (B.unpack doc)
-                  if out == xml
-                      then do
-                          end <- getCurrentTime
-                          let took = end `diffUTCTime` start
-                          hPutStrLn stderr $ "passed"
-                          hPutStrLn stderr $ "took "++showFFloat (Just 3) (realToFrac took) ""++" sec"
-                      else do
-                          hPutStrLn stderr $ "Failed - mismatch:"
-                          putStr out
-              Left error -> do
-                  hPutStrLn stderr $ "FAILED: "++error
-      Nothing -> return ()
-
-main = do
-  xml <- B.readFile "test.xml"
-  main_tree xml
diff --git a/test/example.hs b/test/example.hs
new file mode 100644
--- /dev/null
+++ b/test/example.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+import Text.XML.Expat.Tree
+import Text.XML.Expat.Pickle
+import Text.XML.Expat.Format
+import qualified Data.ByteString.Lazy as L
+
+-- Person name, age and description
+data Person = Person String Int String
+
+xpPerson :: Element t String String => PU t Person
+xpPerson =
+    -- How to wrap and unwrap a Person
+    xpWrap (\((name, age), descr) -> Person name age descr,
+            \(Person name age descr) -> ((name, age), descr)) $
+    xpElem "person"
+        (xpPair
+            (xpAttr "name" xpText0)
+            (xpAttr "age" xpickle))
+        xpText0
+
+people = [
+    Person "Dave" 27 "A fat thin man with long short hair",
+    Person "Jane" 21 "Lives in a white house with green windows"]
+
+main = do
+    L.putStrLn $
+        formatTree stringFlavor $
+            pickleTree (xpElemNodes "people" $ xpList xpPerson) people
+
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,151 @@
+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses #-}
+
+import Text.XML.Expat.Tree
+import Text.XML.Expat.Pickle
+import Text.XML.Expat.Format
+import Data.Tree
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+import Data.ByteString.Internal (w2c, c2w)
+import Control.Monad
+import Data.Maybe
+import Data.Either
+import Data.List
+import Data.Char
+import Debug.Trace
+import Data.Time.Clock
+import Numeric
+import System.IO
+import Data.Monoid
+
+class Key k where
+    keyValue :: k -> String
+
+class Key k => LanguageKey k where
+    isoOf :: k -> String
+    makeLanguageKey :: String -> k
+
+data MnemonicKey = MnemonicKey String
+    deriving Show
+
+instance Key MnemonicKey where
+    keyValue (MnemonicKey str) = str
+
+data LanguageKey k => MultiText k = MultiText {
+        lang         :: k,
+        languageText :: String,
+        timestamp    :: Integer
+    }
+    deriving (Eq, Show)
+
+instance LanguageKey k => XmlPickler (Node String String) (MultiText k) where
+    xpickle = xpMultiText "text"
+
+data SiteLanguageKey = SiteLanguageKey String
+    deriving Show
+
+instance Key SiteLanguageKey where
+    keyValue (SiteLanguageKey a) = a
+
+instance LanguageKey SiteLanguageKey where
+    isoOf = keyValue
+    makeLanguageKey str = SiteLanguageKey str
+
+maybeRead :: Read a => String -> Maybe a
+maybeRead s = case reads s of
+    [(x, "")] -> Just x
+    _         -> Nothing
+
+xpMultiText :: LanguageKey k => String -> PU (Node String String) (MultiText k)
+xpMultiText tagName =
+    xpWrap (
+        (\((lan, tex1, tim), tex2) -> MultiText
+            (makeLanguageKey lan)
+            (if tex2 /= "" then tex2 else fromMaybe "" tex1)
+            (truncate $ fromMaybe 0 ((maybeRead (fromMaybe "0" tim))::Maybe Float))),
+        (\(MultiText lan tex tim) -> ((isoOf lan, Nothing, Just $ show tim), tex))
+    ) $
+    xpElem tagName
+        (xpTriple
+            (xpAttr "lang" xpText0)
+            (xpOption $ xpAttr "text" xpText0)
+            (xpOption $ xpAttr "time" xpText0))
+        xpText0
+
+data LanguageKey k => MultiLanguage k = MultiLanguage {
+        texts :: [MultiText k]
+    }
+    deriving (Eq, Show)
+
+nullMultiLanguage :: LanguageKey k => MultiLanguage k
+nullMultiLanguage = MultiLanguage []
+
+instance XmlPickler [Node String String] (MultiLanguage SiteLanguageKey) where
+    xpickle = xpMultiLanguage "text"
+
+xpMultiLanguage :: LanguageKey k => String -> PU [Node String String] (MultiLanguage k)
+xpMultiLanguage childTagName =
+    xpWrap (
+        (\ts -> MultiLanguage ts),
+        (\(MultiLanguage ts) -> ts)
+    ) $
+    xpList (xpMultiText childTagName)
+
+data Mnemonic = Mnemonic {
+        mnemonicKey   :: MnemonicKey,
+        category      :: String,
+        comment       :: String,
+        multiLanguage :: MultiLanguage SiteLanguageKey,
+        linkTo        :: Maybe MnemonicKey
+    }
+    deriving (Show)
+
+instance XmlPickler (Node String String) Mnemonic where
+    xpickle = xpMnemonic
+
+xpMnemonic :: PU (Node String String) Mnemonic
+xpMnemonic =
+    xpWrap (
+        (\((mne, cat, com, lin), tex) ->
+            Mnemonic (MnemonicKey mne) cat (fromMaybe "" com) tex
+                (liftM (MnemonicKey . ("category."++)) lin)),
+        (\(Mnemonic (MnemonicKey mne) cat com tex lin) ->
+            ((mne, cat, if com == "" then Nothing else Just com,
+                liftM ((fromMaybe "" . stripPrefix "category.") . keyValue) lin), tex))
+    ) $
+    xpElem "mnemonic"
+        (xp4Tuple
+            (xpAttr "name" xpText0)
+            (xpAttr "category" xpText0)
+            (xpOption $ xpAttr "comment" $ xpText0)
+            (xpOption $ xpAttr "link" $ xpText))
+        (xpMultiLanguage "text")
+
+main_tree doc = do
+  start <- getCurrentTime
+  let mTree = parseTree' stringFlavor Nothing doc
+  case mTree of
+      Right tree -> do
+          let pickler = xpElemNodes "mnemonics" $ xpList xpMnemonic
+          let eMnems = unpickleTree pickler tree
+          case eMnems of
+              Right mnems -> do
+                  let pickled = pickleTree pickler mnems
+                      xml = mconcat . L.toChunks $ formatTree stringFlavor pickled `mappend` L.pack (map c2w "\n")
+                  if xml == doc
+                      then do
+                          end <- getCurrentTime
+                          let took = end `diffUTCTime` start
+                          hPutStrLn stderr $ "passed"
+                          hPutStrLn stderr $ "took "++showFFloat (Just 3) (realToFrac took) ""++" sec"
+                      else do
+                          hPutStrLn stderr $ "Failed - mismatch:"
+                          B.putStr xml
+              Left error -> do
+                  hPutStrLn stderr $ "FAILED: "++error
+      Left error -> do
+              hPutStrLn stderr $ "FAILED: "++show error
+
+main = do
+  xml <- B.readFile "test.xml"
+  main_tree xml
