aboutsummaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Readers/Odt/Generic
diff options
context:
space:
mode:
Diffstat (limited to 'src/Text/Pandoc/Readers/Odt/Generic')
-rw-r--r--src/Text/Pandoc/Readers/Odt/Generic/Fallible.hs260
-rw-r--r--src/Text/Pandoc/Readers/Odt/Generic/Namespaces.hs62
-rw-r--r--src/Text/Pandoc/Readers/Odt/Generic/SetMap.hs48
-rw-r--r--src/Text/Pandoc/Readers/Odt/Generic/Utils.hs171
-rw-r--r--src/Text/Pandoc/Readers/Odt/Generic/XMLConverter.hs1064
5 files changed, 1605 insertions, 0 deletions
diff --git a/src/Text/Pandoc/Readers/Odt/Generic/Fallible.hs b/src/Text/Pandoc/Readers/Odt/Generic/Fallible.hs
new file mode 100644
index 000000000..5922164c9
--- /dev/null
+++ b/src/Text/Pandoc/Readers/Odt/Generic/Fallible.hs
@@ -0,0 +1,260 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+{-
+Copyright (C) 2015 Martin Linnemann <theCodingMarlin@googlemail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-}
+
+{- |
+ Module : Text.Pandoc.Readers.Odt.Generic.Fallible
+ Copyright : Copyright (C) 2015 Martin Linnemann
+ License : GNU GPL, version 2 or above
+
+ Maintainer : Martin Linnemann <theCodingMarlin@googlemail.com>
+ Stability : alpha
+ Portability : portable
+
+Data types and utilities representing failure. Most of it is based on the
+"Either" type in its usual configuration (left represents failure).
+
+In most cases, the failure type is implied or required to be a "Monoid".
+
+The choice of "Either" instead of a custom type makes it easier to write
+compatible instances of "ArrowChoice".
+-}
+
+-- We export everything
+module Text.Pandoc.Readers.Odt.Generic.Fallible where
+
+import Control.Applicative
+import Control.Monad
+
+import qualified Data.Foldable as F
+import Data.Monoid
+
+-- | Default for now. Will probably become a class at some point.
+type Failure = ()
+
+type Fallible a = Either Failure a
+
+
+-- | False -> Left (), True -> Right ()
+boolToEither :: Bool -> Fallible ()
+boolToEither False = Left ()
+boolToEither True = Right ()
+
+-- | False -> Left (), True -> Right ()
+boolToChoice :: Bool -> Fallible ()
+boolToChoice False = Left ()
+boolToChoice True = Right ()
+
+--
+maybeToEither :: Maybe a -> Fallible a
+maybeToEither (Just a) = Right a
+maybeToEither Nothing = Left ()
+
+--
+eitherToMaybe :: Either _l a -> Maybe a
+eitherToMaybe (Left _) = Nothing
+eitherToMaybe (Right a) = Just a
+
+-- | > untagEither === either id id
+untagEither :: Either a a -> a
+untagEither (Left a) = a
+untagEither (Right a) = a
+
+-- | > fromLeft f === either f id
+fromLeft :: (a -> b) -> Either a b -> b
+fromLeft f (Left a) = f a
+fromLeft _ (Right b) = b
+
+-- | > fromRight f === either id f
+fromRight :: (a -> b) -> Either b a -> b
+fromRight _ (Left b) = b
+fromRight f (Right a) = f a
+
+-- | > recover a === fromLeft (const a) === either (const a) id
+recover :: a -> Either _f a -> a
+recover a (Left _) = a
+recover _ (Right a) = a
+
+-- | I would love to use 'fail'. Alas, 'Monad.fail'...
+failWith :: failure -> Either failure _x
+failWith f = Left f
+
+--
+failEmpty :: (Monoid failure) => Either failure _x
+failEmpty = failWith mempty
+
+--
+succeedWith :: a -> Either _x a
+succeedWith = Right
+
+--
+collapseEither :: Either failure (Either failure x)
+ -> Either failure x
+collapseEither (Left f ) = Left f
+collapseEither (Right (Left f)) = Left f
+collapseEither (Right (Right x)) = Right x
+
+-- | If either of the values represents an error, the result is a
+-- (possibly combined) error. If both values represent a success,
+-- both are returned.
+chooseMin :: (Monoid a) => Either a b -> Either a b' -> Either a (b,b')
+chooseMin = chooseMinWith (,)
+
+-- | If either of the values represents an error, the result is a
+-- (possibly combined) error. If both values represent a success,
+-- a combination is returned.
+chooseMinWith :: (Monoid a) => (b -> b' -> c)
+ -> Either a b
+ -> Either a b'
+ -> Either a c
+chooseMinWith (><) (Right a) (Right b) = Right $ a >< b
+chooseMinWith _ (Left a) (Left b) = Left $ a <> b
+chooseMinWith _ (Left a) _ = Left a
+chooseMinWith _ _ (Left b) = Left b
+
+-- | If either of the values represents a non-error, the result is a
+-- (possibly combined) non-error. If both values represent an error, an error
+-- is returned.
+chooseMax :: (Monoid a, Monoid b) => Either a b -> Either a b -> Either a b
+chooseMax = chooseMaxWith (<>)
+
+-- | If either of the values represents a non-error, the result is a
+-- (possibly combined) non-error. If both values represent an error, an error
+-- is returned.
+chooseMaxWith :: (Monoid a) => (b -> b -> b)
+ -> Either a b
+ -> Either a b
+ -> Either a b
+chooseMaxWith (><) (Right a) (Right b) = Right $ a >< b
+chooseMaxWith _ (Left a) (Left b) = Left $ a <> b
+chooseMaxWith _ (Right a) _ = Right a
+chooseMaxWith _ _ (Right b) = Right b
+
+
+-- | Class of containers that can escalate contained 'Either's.
+-- The word "Vector" is meant in the sense of a disease transmitter.
+class ChoiceVector v where
+ spreadChoice :: v (Either f a) -> Either f (v a)
+
+-- Let's do a few examples first
+
+instance ChoiceVector Maybe where
+ spreadChoice (Just (Left f)) = Left f
+ spreadChoice (Just (Right x)) = Right (Just x)
+ spreadChoice Nothing = Right Nothing
+
+instance ChoiceVector (Either l) where
+ spreadChoice (Right (Left f)) = Left f
+ spreadChoice (Right (Right x)) = Right (Right x)
+ spreadChoice (Left x ) = Right (Left x)
+
+instance ChoiceVector ((,) a) where
+ spreadChoice (_, Left f) = Left f
+ spreadChoice (x, Right y) = Right (x,y)
+ -- Wasn't there a newtype somewhere with the elements flipped?
+
+--
+-- More instances later, first some discussion.
+--
+-- I'll have to freshen up on type system details to see how (or if) to do
+-- something like
+--
+-- > instance (ChoiceVector a, ChoiceVector b) => ChoiceVector (a b) where
+-- > :
+--
+-- But maybe it would be even better to use something like
+--
+-- > class ChoiceVector v v' f | v -> v' f where
+-- > spreadChoice :: v -> Either f v'
+--
+-- That way, more places in @v@ could spread the cheer, e.g.:
+--
+-- As before:
+-- -- ( a , Either f b) (a , b) f
+-- > instance ChoiceVector ((,) a (Either f b)) ((,) a b) f where
+-- > spreadChoice (_, Left f) = Left f
+-- > spreadChoice (a, Right b) = Right (a,b)
+--
+-- But also:
+-- -- ( Either f a , b) (a , b) f
+-- > instance ChoiceVector ((,) (Either f a) b) ((,) a b) f where
+-- > spreadChoice (Right a,b) = Right (a,b)
+-- > spreadChoice (Left f,_) = Left f
+--
+-- And maybe even:
+-- -- ( Either f a , Either f b) (a , b) f
+-- > instance ChoiceVector ((,) (Either f a) (Either f b)) ((,) a b) f where
+-- > spreadChoice (Right a , Right b) = Right (a,b)
+-- > spreadChoice (Left f , _ ) = Left f
+-- > spreadChoice ( _ , Left f) = Left f
+--
+-- Of course that would lead to a lot of overlapping instances...
+-- But I can't think of a different way. A selector function might help,
+-- but not even a "Data.Traversable" is powerful enough for that.
+-- But maybe someone has already solved all this with a lens library.
+--
+-- Well, it's an interesting academic question. But for practical purposes,
+-- I have more than enough right now.
+
+instance ChoiceVector ((,,) a b) where
+ spreadChoice (_,_, Left f) = Left f
+ spreadChoice (a,b, Right x) = Right (a,b,x)
+
+instance ChoiceVector ((,,,) a b c) where
+ spreadChoice (_,_,_, Left f) = Left f
+ spreadChoice (a,b,c, Right x) = Right (a,b,c,x)
+
+instance ChoiceVector ((,,,,) a b c d) where
+ spreadChoice (_,_,_,_, Left f) = Left f
+ spreadChoice (a,b,c,d, Right x) = Right (a,b,c,d,x)
+
+instance ChoiceVector (Const a) where
+ spreadChoice (Const c) = Right (Const c) -- need to repackage because of implicit types
+
+-- | Fails on the first error
+instance ChoiceVector [] where
+ spreadChoice = sequence -- using the monad instance of Either.
+ -- Could be generalized to "Data.Traversable" - but why play
+ -- with UndecidableInstances unless this is really needed.
+
+-- | Wrapper for a list. While the normal list instance of 'ChoiceVector'
+-- fails whenever it can, this type will never fail.
+newtype SuccessList a = SuccessList { collectNonFailing :: [a] }
+ deriving ( Eq, Ord, Show )
+
+instance ChoiceVector SuccessList where
+ spreadChoice = Right . SuccessList . (foldr unTagRight []) . collectNonFailing
+ where unTagRight (Right x) = (x:)
+ unTagRight _ = id
+
+-- | Like 'catMaybes', but for 'Either'.
+collectRights :: [Either _l r] -> [r]
+collectRights = collectNonFailing . untag . spreadChoice . SuccessList
+ where untag = fromLeft (error "Unexpected Left")
+
+-- | A version of 'collectRights' generalized to other containers. The
+-- container must be both "reducible" and "buildable". Most general containers
+-- should fullfill these requirements, but there is no single typeclass
+-- (that I know of) for that.
+-- Therefore, they are split between 'Foldable' and 'MonadPlus'.
+-- (Note that 'Data.Traversable.Traversable' alone would not be enough, either.)
+collectRightsF :: (F.Foldable c, MonadPlus c) => c (Either _l r) -> c r
+collectRightsF = F.foldr unTagRight mzero
+ where unTagRight (Right x) = mplus $ return x
+ unTagRight _ = id
diff --git a/src/Text/Pandoc/Readers/Odt/Generic/Namespaces.hs b/src/Text/Pandoc/Readers/Odt/Generic/Namespaces.hs
new file mode 100644
index 000000000..82ae3e20e
--- /dev/null
+++ b/src/Text/Pandoc/Readers/Odt/Generic/Namespaces.hs
@@ -0,0 +1,62 @@
+{-
+Copyright (C) 2015 Martin Linnemann <theCodingMarlin@googlemail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-}
+
+{- |
+ Module : Text.Pandoc.Readers.Odt.Generic.Namespaces
+ Copyright : Copyright (C) 2015 Martin Linnemann
+ License : GNU GPL, version 2 or above
+
+ Maintainer : Martin Linnemann <theCodingMarlin@googlemail.com>
+ Stability : alpha
+ Portability : portable
+
+A class containing a set of namespace identifiers. Used to convert between
+typesafe Haskell namespace identifiers and unsafe "real world" namespaces.
+-}
+
+module Text.Pandoc.Readers.Odt.Generic.Namespaces where
+
+import qualified Data.Map as M
+
+--
+type NameSpaceIRI = String
+
+--
+type NameSpaceIRIs nsID = M.Map nsID NameSpaceIRI
+
+--
+class (Eq nsID, Ord nsID) => NameSpaceID nsID where
+
+ -- | Given a IRI, possibly update the map and return the id of the namespace.
+ -- May fail if the namespace is unknown and the application does not
+ -- allow unknown namespaces.
+ getNamespaceID :: NameSpaceIRI
+ -> NameSpaceIRIs nsID
+ -> Maybe (NameSpaceIRIs nsID, nsID)
+ -- | Given a namespace id, lookup its IRI. May be overriden for performance.
+ getIRI :: nsID
+ -> NameSpaceIRIs nsID
+ -> Maybe NameSpaceIRI
+ -- | The root element of an XML document has a namespace, too, and the
+ -- "XML.Light-parser" is eager to remove the corresponding namespace
+ -- attribute.
+ -- As a result, at least this root namespace must be provided.
+ getInitialIRImap :: NameSpaceIRIs nsID
+
+ getIRI = M.lookup
+ getInitialIRImap = M.empty
diff --git a/src/Text/Pandoc/Readers/Odt/Generic/SetMap.hs b/src/Text/Pandoc/Readers/Odt/Generic/SetMap.hs
new file mode 100644
index 000000000..afd7d616c
--- /dev/null
+++ b/src/Text/Pandoc/Readers/Odt/Generic/SetMap.hs
@@ -0,0 +1,48 @@
+{-
+Copyright (C) 2015 Martin Linnemann <theCodingMarlin@googlemail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-}
+
+{- |
+ Module : Text.Pandoc.Readers.Odt.Generic.SetMap
+ Copyright : Copyright (C) 2015 Martin Linnemann
+ License : GNU GPL, version 2 or above
+
+ Maintainer : Martin Linnemann <theCodingMarlin@googlemail.com>
+ Stability : alpha
+ Portability : portable
+
+A map of values to sets of values.
+-}
+
+module Text.Pandoc.Readers.Odt.Generic.SetMap where
+
+import qualified Data.Map as M
+import qualified Data.Set as S
+
+type SetMap k v = M.Map k (S.Set v)
+
+empty :: SetMap k v
+empty = M.empty
+
+fromList :: (Ord k, Ord v) => [(k,v)] -> SetMap k v
+fromList = foldr (uncurry insert) empty
+
+insert :: (Ord k, Ord v) => k -> v -> SetMap k v -> SetMap k v
+insert key value setMap = M.insertWith S.union key (S.singleton value) setMap
+
+union3 :: (Ord k) => SetMap k v -> SetMap k v -> SetMap k v -> SetMap k v
+union3 sm1 sm2 sm3 = sm1 `M.union` sm2 `M.union` sm3
diff --git a/src/Text/Pandoc/Readers/Odt/Generic/Utils.hs b/src/Text/Pandoc/Readers/Odt/Generic/Utils.hs
new file mode 100644
index 000000000..6c10ed61d
--- /dev/null
+++ b/src/Text/Pandoc/Readers/Odt/Generic/Utils.hs
@@ -0,0 +1,171 @@
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE ViewPatterns #-}
+
+{-
+Copyright (C) 2015 Martin Linnemann <theCodingMarlin@googlemail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-}
+
+{- |
+ Module : Text.Pandoc.Reader.Odt.Generic.Utils
+ Copyright : Copyright (C) 2015 Martin Linnemann
+ License : GNU GPL, version 2 or above
+
+ Maintainer : Martin Linnemann <theCodingMarlin@googlemail.com>
+ Stability : alpha
+ Portability : portable
+
+General utility functions for the odt reader.
+-}
+
+module Text.Pandoc.Readers.Odt.Generic.Utils
+( uncurry3
+, uncurry4
+, uncurry5
+, uncurry6
+, uncurry7
+, uncurry8
+, swap
+, reverseComposition
+, bool
+, tryToRead
+, Lookupable(..)
+, readLookupables
+, readLookupable
+, readPercent
+, findBy
+, swing
+, composition
+) where
+
+import Control.Category ( Category, (>>>), (<<<) )
+import qualified Control.Category as Cat ( id )
+import Control.Monad ( msum )
+
+import qualified Data.Foldable as F ( Foldable, foldr )
+import Data.Maybe
+
+
+-- | Aequivalent to
+-- > foldr (.) id
+-- where '(.)' are 'id' are the ones from "Control.Category"
+-- and 'foldr' is the one from "Data.Foldable".
+-- The noun-form was chosen to be consistend with 'sum', 'product' etc
+-- based on the discussion at
+-- <https://groups.google.com/forum/#!topic/haskell-cafe/VkOZM1zaHOI>
+-- (that I was not part of)
+composition :: (Category cat, F.Foldable f) => f (cat a a) -> cat a a
+composition = F.foldr (<<<) Cat.id
+
+-- | Aequivalent to
+-- > foldr (flip (.)) id
+-- where '(.)' are 'id' are the ones from "Control.Category"
+-- and 'foldr' is the one from "Data.Foldable".
+-- A reversed version of 'composition'.
+reverseComposition :: (Category cat, F.Foldable f) => f (cat a a) -> cat a a
+reverseComposition = F.foldr (>>>) Cat.id
+
+-- | 'Either' has 'either', 'Maybe' has 'maybe'. 'Bool' should have 'bool'.
+-- Note that the first value is selected if the boolean value is 'False'.
+-- That makes 'bool' consistent with the other two. Also, 'bool' now takes its
+-- arguments in the exact opposite order compared to the normal if construct.
+bool :: a -> a -> Bool -> a
+bool x _ False = x
+bool _ x True = x
+
+-- | This function often makes it possible to switch values with the functions
+-- that are applied to them.
+--
+-- Examples:
+-- > swing map :: [a -> b] -> a -> [b]
+-- > swing any :: [a -> Bool] -> a -> Bool
+-- > swing foldr :: b -> a -> [a -> b -> b] -> b
+-- > swing scanr :: c -> a -> [a -> c -> c] -> c
+-- > swing zipWith :: [a -> b -> c] -> a -> [b] -> [c]
+-- > swing find :: [a -> Bool] -> a -> Maybe (a -> Bool)
+--
+-- Stolen from <https://wiki.haskell.org/Pointfree>
+swing :: (((a -> b) -> b) -> c -> d) -> c -> a -> d
+swing = flip.(.flip id)
+-- swing f c a = f ($ a) c
+
+
+-- | Alternative to 'read'/'reads'. The former of these throws errors
+-- (nobody wants that) while the latter returns "to much" for simple purposes.
+-- This function instead applies 'reads' and returns the first match (if any)
+-- in a 'Maybe'.
+tryToRead :: (Read r) => String -> Maybe r
+tryToRead = reads >>> listToMaybe >>> fmap fst
+
+-- | A version of 'reads' that requires a '%' sign after the number
+readPercent :: ReadS Int
+readPercent s = [ (i,s') | (i , r ) <- reads s
+ , ("%" , s') <- lex r
+ ]
+
+-- | Data that can be looked up.
+-- This is mostly a utility to read data with kind *.
+class Lookupable a where
+ lookupTable :: [(String, a)]
+
+-- | The idea is to use this function as if there was a declaration like
+--
+-- > instance (Lookupable a) => (Read a) where
+-- > readsPrec _ = readLookupables
+-- .
+-- But including this code in this form would need UndecideableInstances.
+-- That is a bad idea. Luckily 'readLookupable' (without the s at the end)
+-- can be used directly in almost any case.
+readLookupables :: (Lookupable a) => String -> [(a,String)]
+readLookupables s = [ (a,rest) | (word,rest) <- lex s,
+ let result = lookup word lookupTable,
+ isJust result,
+ let Just a = result
+ ]
+
+-- | Very similar to a simple 'lookup' in the 'lookupTable', but with a lexer.
+readLookupable :: (Lookupable a) => String -> Maybe a
+readLookupable s = msum
+ $ map ((`lookup` lookupTable).fst)
+ $ lex s
+
+uncurry3 :: (a->b->c -> z) -> (a,b,c ) -> z
+uncurry4 :: (a->b->c->d -> z) -> (a,b,c,d ) -> z
+uncurry5 :: (a->b->c->d->e -> z) -> (a,b,c,d,e ) -> z
+uncurry6 :: (a->b->c->d->e->f -> z) -> (a,b,c,d,e,f ) -> z
+uncurry7 :: (a->b->c->d->e->f->g -> z) -> (a,b,c,d,e,f,g ) -> z
+uncurry8 :: (a->b->c->d->e->f->g->h -> z) -> (a,b,c,d,e,f,g,h) -> z
+
+uncurry3 fun (a,b,c ) = fun a b c
+uncurry4 fun (a,b,c,d ) = fun a b c d
+uncurry5 fun (a,b,c,d,e ) = fun a b c d e
+uncurry6 fun (a,b,c,d,e,f ) = fun a b c d e f
+uncurry7 fun (a,b,c,d,e,f,g ) = fun a b c d e f g
+uncurry8 fun (a,b,c,d,e,f,g,h) = fun a b c d e f g h
+
+swap :: (a,b) -> (b,a)
+swap (a,b) = (b,a)
+
+-- | A version of "Data.List.find" that uses a converter to a Maybe instance.
+-- The returned value is the first which the converter returns in a 'Just'
+-- wrapper.
+findBy :: (a -> Maybe b) -> [a] -> Maybe b
+findBy _ [] = Nothing
+findBy f ((f -> Just x):_ ) = Just x
+findBy f ( _:xs) = findBy f xs
+
diff --git a/src/Text/Pandoc/Readers/Odt/Generic/XMLConverter.hs b/src/Text/Pandoc/Readers/Odt/Generic/XMLConverter.hs
new file mode 100644
index 000000000..ec7e0ea5e
--- /dev/null
+++ b/src/Text/Pandoc/Readers/Odt/Generic/XMLConverter.hs
@@ -0,0 +1,1064 @@
+{-# LANGUAGE Arrows #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE RecordWildCards #-}
+
+{-
+Copyright (C) 2015 Martin Linnemann <theCodingMarlin@googlemail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-}
+
+{- |
+ Module : Text.Pandoc.Readers.Odt.Generic.XMLConverter
+ Copyright : Copyright (C) 2015 Martin Linnemann
+ License : GNU GPL, version 2 or above
+
+ Maintainer : Martin Linnemann <theCodingMarlin@googlemail.com>
+ Stability : alpha
+ Portability : portable
+
+A generalized XML parser based on stateful arrows.
+It might be sufficient to define this reader as a comonad, but there is
+not a lot of use in trying.
+-}
+
+module Text.Pandoc.Readers.Odt.Generic.XMLConverter
+( ElementName
+, XMLConverterState
+, XMLConverter
+, FallibleXMLConverter
+, swapPosition
+, runConverter
+, runConverter''
+, runConverter'
+, runConverterF'
+, runConverterF
+, getCurrentElement
+, getExtraState
+, setExtraState
+, modifyExtraState
+, convertingExtraState
+, producingExtraState
+, lookupNSiri
+, lookupNSprefix
+, readNSattributes
+, elemName
+, elemNameIs
+, strContent
+, elContent
+, currentElem
+, currentElemIs
+, expectElement
+, elChildren
+, findChildren
+, filterChildren
+, filterChildrenName
+, findChild'
+, findChild
+, filterChild'
+, filterChild
+, filterChildName'
+, filterChildName
+, isSet
+, isSet'
+, isSetWithDefault
+, hasAttrValueOf'
+, failIfNotAttrValueOf
+, isThatTheAttrValue
+, searchAttrIn
+, searchAttrWith
+, searchAttr
+, lookupAttr
+, lookupAttr'
+, lookupAttrWithDefault
+, lookupDefaultingAttr
+, findAttr'
+, findAttr
+, findAttrWithDefault
+, readAttr
+, readAttr'
+, readAttrWithDefault
+, getAttr
+-- , (>/<)
+-- , (?>/<)
+, executeIn
+, collectEvery
+, withEveryL
+, withEvery
+, tryAll
+, tryAll'
+, IdXMLConverter
+, MaybeEConverter
+, ElementMatchConverter
+, MaybeCConverter
+, ContentMatchConverter
+, makeMatcherE
+, makeMatcherC
+, prepareMatchersE
+, prepareMatchersC
+, matchChildren
+, matchContent''
+, matchContent'
+, matchContent
+) where
+
+import Control.Applicative hiding ( liftA, liftA2 )
+import Control.Monad ( MonadPlus )
+import Control.Arrow
+
+import qualified Data.Map as M
+import qualified Data.Foldable as F
+import Data.Default
+import Data.Monoid ( Monoid )
+import Data.Maybe
+
+import qualified Text.XML.Light as XML
+
+import Text.Pandoc.Readers.Odt.Arrows.State
+import Text.Pandoc.Readers.Odt.Arrows.Utils
+
+import Text.Pandoc.Readers.Odt.Generic.Namespaces
+import Text.Pandoc.Readers.Odt.Generic.Utils
+import Text.Pandoc.Readers.Odt.Generic.Fallible
+
+--------------------------------------------------------------------------------
+-- Basis types for readability
+--------------------------------------------------------------------------------
+
+--
+type ElementName = String
+type AttributeName = String
+type AttributeValue = String
+
+--
+type NameSpacePrefix = String
+
+--
+type NameSpacePrefixes nsID = M.Map nsID NameSpacePrefix
+
+--------------------------------------------------------------------------------
+-- Main converter state
+--------------------------------------------------------------------------------
+
+-- GADT so some of the NameSpaceID restrictions can be deduced
+data XMLConverterState nsID extraState where
+ XMLConverterState :: NameSpaceID nsID
+ => { -- | A stack of parent elements. The top element is the current one.
+ -- Arguably, a real Zipper would be better. But that is an
+ -- optimization that can be made at a later time, e.g. when
+ -- replacing Text.XML.Light.
+ parentElements :: [XML.Element]
+ -- | A map from internal namespace IDs to the namespace prefixes
+ -- used in XML elements
+ , namespacePrefixes :: NameSpacePrefixes nsID
+ -- | A map from internal namespace IDs to namespace IRIs
+ -- (Only necessary for matching namespace IDs and prefixes)
+ , namespaceIRIs :: NameSpaceIRIs nsID
+ -- | A place to put "something else". This feature is used heavily
+ -- to keep the main code cleaner. More specifically, the main reader
+ -- is divided into different stages. Each stage lifts something up
+ -- here, which the next stage can then use. This could of course be
+ -- generalized to a state-tree or used for the namespace IRIs. The
+ -- border between states and values is an imaginary one, after all.
+ -- But the separation as it is seems to be enough for now.
+ , moreState :: extraState
+ }
+ -> XMLConverterState nsID extraState
+
+--
+createStartState :: (NameSpaceID nsID)
+ => XML.Element
+ -> extraState
+ -> XMLConverterState nsID extraState
+createStartState element extraState =
+ XMLConverterState
+ { parentElements = [element]
+ , namespacePrefixes = M.empty
+ , namespaceIRIs = getInitialIRImap
+ , moreState = extraState
+ }
+
+-- | Functor over extra state
+instance Functor (XMLConverterState nsID) where
+ fmap f ( XMLConverterState parents prefixes iRIs extraState )
+ = XMLConverterState parents prefixes iRIs (f extraState)
+
+--
+replaceExtraState :: extraState
+ -> XMLConverterState nsID _x
+ -> XMLConverterState nsID extraState
+replaceExtraState x s
+ = fmap (const x) s
+
+--
+currentElement :: XMLConverterState nsID extraState
+ -> XML.Element
+currentElement state = head (parentElements state)
+
+-- | Replace the current position by another, modifying the extra state
+-- in the process
+swapPosition :: (extraState -> extraState')
+ -> [XML.Element]
+ -> XMLConverterState nsID extraState
+ -> XMLConverterState nsID extraState'
+swapPosition f stack state
+ = state { parentElements = stack
+ , moreState = f (moreState state)
+ }
+
+-- | Replace the current position by another, modifying the extra state
+-- in the process
+swapStack' :: XMLConverterState nsID extraState
+ -> [XML.Element]
+ -> ( XMLConverterState nsID extraState , [XML.Element] )
+swapStack' state stack
+ = ( state { parentElements = stack }
+ , parentElements state
+ )
+
+--
+pushElement :: XML.Element
+ -> XMLConverterState nsID extraState
+ -> XMLConverterState nsID extraState
+pushElement e state = state { parentElements = e:(parentElements state) }
+
+-- | Pop the top element from the call stack, unless it is the last one.
+popElement :: XMLConverterState nsID extraState
+ -> Maybe (XMLConverterState nsID extraState)
+popElement state
+ | _:es@(_:_) <- parentElements state = Just $ state { parentElements = es }
+ | otherwise = Nothing
+
+--------------------------------------------------------------------------------
+-- Main type
+--------------------------------------------------------------------------------
+
+-- It might be a good idea to pack the converters in a GADT
+-- Downside: data instead of type
+-- Upside: 'Failure' could be made a parameter as well.
+
+--
+type XMLConverter nsID extraState input output
+ = ArrowState (XMLConverterState nsID extraState ) input output
+
+type FallibleXMLConverter nsID extraState input output
+ = XMLConverter nsID extraState input (Fallible output)
+
+--
+runConverter :: XMLConverter nsID extraState input output
+ -> XMLConverterState nsID extraState
+ -> input
+ -> output
+runConverter converter state input = snd $ runArrowState converter (state,input)
+
+--
+runConverter'' :: (NameSpaceID nsID)
+ => XMLConverter nsID extraState (Fallible ()) output
+ -> extraState
+ -> XML.Element
+ -> output
+runConverter'' converter extraState element = runConverter (readNSattributes >>> converter) (createStartState element extraState) ()
+
+runConverter' :: (NameSpaceID nsID)
+ => FallibleXMLConverter nsID extraState () success
+ -> extraState
+ -> XML.Element
+ -> Fallible success
+runConverter' converter extraState element = runConverter (readNSattributes >>? converter) (createStartState element extraState) ()
+
+--
+runConverterF' :: FallibleXMLConverter nsID extraState x y
+ -> XMLConverterState nsID extraState
+ -> Fallible x -> Fallible y
+runConverterF' a s e = runConverter (returnV e >>? a) s e
+
+--
+runConverterF :: (NameSpaceID nsID)
+ => FallibleXMLConverter nsID extraState XML.Element x
+ -> extraState
+ -> Fallible XML.Element -> Fallible x
+runConverterF a s = either failWith
+ (\e -> runConverter a (createStartState e s) e)
+
+--
+getCurrentElement :: XMLConverter nsID extraState x XML.Element
+getCurrentElement = extractFromState currentElement
+
+--
+getExtraState :: XMLConverter nsID extraState x extraState
+getExtraState = extractFromState moreState
+
+--
+setExtraState :: XMLConverter nsID extraState extraState extraState
+setExtraState = withState $ \state extra
+ -> (replaceExtraState extra state , extra)
+
+
+-- | Lifts a function to the extra state.
+modifyExtraState :: (extraState -> extraState)
+ -> XMLConverter nsID extraState x x
+modifyExtraState = modifyState.fmap
+
+
+-- | First sets the extra state to the new value. Then modifies the original
+-- extra state with a converter that uses the new state. Finally, the
+-- intermediate state is dropped and the extra state is lifted into the
+-- state as it was at the beginning of the function.
+-- As a result, exactly the extra state and nothing else is changed.
+-- The resulting converter even behaves like an identity converter on the
+-- value level.
+--
+-- (The -ing form is meant to be mnemonic in a sequence of arrows as in
+-- convertingExtraState () converter >>> doOtherStuff)
+--
+convertingExtraState :: extraState'
+ -> FallibleXMLConverter nsID extraState' extraState extraState
+ -> FallibleXMLConverter nsID extraState x x
+convertingExtraState v a = withSubStateF setVAsExtraState modifyWithA
+ where
+ setVAsExtraState = liftAsSuccess $ extractFromState id >>^ replaceExtraState v
+ modifyWithA = keepingTheValue (moreState ^>> a)
+ >>^ spreadChoice >>?§ flip replaceExtraState
+
+-- | First sets the extra state to the new value. Then produces a new
+-- extra state with a converter that uses the new state. Finally, the
+-- intermediate state is dropped and the extra state is lifted into the
+-- state as it was at the beginning of the function.
+-- As a result, exactly the extra state and nothing else is changed.
+-- The resulting converter even behaves like an identity converter on the
+-- value level.
+--
+-- Aequivalent to
+--
+-- > \v x a -> convertingExtraState v (returnV x >>> a)
+--
+-- (The -ing form is meant to be mnemonic in a sequence of arrows as in
+-- producingExtraState () () producer >>> doOtherStuff)
+--
+producingExtraState :: extraState'
+ -> a
+ -> FallibleXMLConverter nsID extraState' a extraState
+ -> FallibleXMLConverter nsID extraState x x
+producingExtraState v x a = convertingExtraState v (returnV x >>> a)
+
+
+--------------------------------------------------------------------------------
+-- Work in namespaces
+--------------------------------------------------------------------------------
+
+-- | Arrow version of 'getIRI'
+lookupNSiri :: (NameSpaceID nsID)
+ => nsID
+ -> XMLConverter nsID extraState x (Maybe NameSpaceIRI)
+lookupNSiri nsID = extractFromState
+ $ \state -> getIRI nsID $ namespaceIRIs state
+
+--
+lookupNSprefix :: (NameSpaceID nsID)
+ => nsID
+ -> XMLConverter nsID extraState x (Maybe NameSpacePrefix)
+lookupNSprefix nsID = extractFromState
+ $ \state -> M.lookup nsID $ namespacePrefixes state
+
+-- | Extracts namespace attributes from the current element and tries to
+-- update the current mapping accordingly
+readNSattributes :: (NameSpaceID nsID)
+ => FallibleXMLConverter nsID extraState x ()
+readNSattributes = fromState $ \state -> maybe (state, failEmpty )
+ ( , succeedWith ())
+ (extractNSAttrs state )
+ where
+ extractNSAttrs :: (NameSpaceID nsID)
+ => XMLConverterState nsID extraState
+ -> Maybe (XMLConverterState nsID extraState)
+ extractNSAttrs startState
+ = foldl (\state d -> state >>= addNS d)
+ (Just startState)
+ nsAttribs
+ where nsAttribs = mapMaybe readNSattr (XML.elAttribs element)
+ element = currentElement startState
+ readNSattr (XML.Attr (XML.QName name _ (Just "xmlns")) iri)
+ = Just (name, iri)
+ readNSattr _ = Nothing
+ addNS (prefix, iri) state = fmap updateState
+ $ getNamespaceID iri
+ $ namespaceIRIs state
+ where updateState (iris,nsID)
+ = state { namespaceIRIs = iris
+ , namespacePrefixes = M.insert nsID prefix
+ $ namespacePrefixes state
+ }
+
+--------------------------------------------------------------------------------
+-- Common namespace accessors
+--------------------------------------------------------------------------------
+
+-- | Given a namespace id and an element name, creates a 'XML.QName' for
+-- internal use
+elemName :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> XMLConverter nsID extraState x XML.QName
+elemName nsID name = lookupNSiri nsID
+ &&& lookupNSprefix nsID
+ >>§ XML.QName name
+
+-- | Checks if a given element matches both a specified namespace id
+-- and a specified element name
+elemNameIs :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> XMLConverter nsID extraState XML.Element Bool
+elemNameIs nsID name = keepingTheValue (lookupNSiri nsID) >>§ hasThatName
+ where hasThatName e iri = let elName = XML.elName e
+ in XML.qName elName == name
+ && XML.qURI elName == iri
+
+--------------------------------------------------------------------------------
+-- General content
+--------------------------------------------------------------------------------
+
+--
+strContent :: XMLConverter nsID extraState x String
+strContent = getCurrentElement
+ >>^ XML.strContent
+
+--
+elContent :: XMLConverter nsID extraState x [XML.Content]
+elContent = getCurrentElement
+ >>^ XML.elContent
+
+--------------------------------------------------------------------------------
+-- Current element
+--------------------------------------------------------------------------------
+
+--
+currentElem :: XMLConverter nsID extraState x (XML.QName)
+currentElem = getCurrentElement
+ >>^ XML.elName
+
+currentElemIs :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> XMLConverter nsID extraState x Bool
+currentElemIs nsID name = getCurrentElement
+ >>> elemNameIs nsID name
+
+
+
+{-
+currentElemIs'' nsID name = ( (getCurrentElement >>^ XML.elName >>>
+ (XML.qName >>^ (&&).(== name) )
+ ^&&&^
+ (XML.qIRI >>^ (==) )
+ ) >>§ (.)
+ ) &&& lookupNSiri nsID >>§ ($)
+-}
+
+--
+expectElement :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState x ()
+expectElement nsID name = currentElemIs nsID name
+ >>^ boolToChoice
+
+--------------------------------------------------------------------------------
+-- Chilren
+--------------------------------------------------------------------------------
+
+--
+elChildren :: XMLConverter nsID extraState x [XML.Element]
+elChildren = getCurrentElement
+ >>^ XML.elChildren
+
+--
+findChildren :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> XMLConverter nsID extraState x [XML.Element]
+findChildren nsID name = elemName nsID name
+ &&& getCurrentElement
+ >>§ XML.findChildren
+
+--
+filterChildren :: (XML.Element -> Bool)
+ -> XMLConverter nsID extraState x [XML.Element]
+filterChildren p = getCurrentElement
+ >>^ XML.filterChildren p
+
+--
+filterChildrenName :: (XML.QName -> Bool)
+ -> XMLConverter nsID extraState x [XML.Element]
+filterChildrenName p = getCurrentElement
+ >>^ XML.filterChildrenName p
+
+--
+findChild' :: (NameSpaceID nsID)
+ => nsID
+ -> ElementName
+ -> XMLConverter nsID extraState x (Maybe XML.Element)
+findChild' nsID name = elemName nsID name
+ &&& getCurrentElement
+ >>§ XML.findChild
+
+--
+findChild :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState x XML.Element
+findChild nsID name = findChild' nsID name
+ >>> maybeToChoice
+
+--
+filterChild' :: (XML.Element -> Bool)
+ -> XMLConverter nsID extraState x (Maybe XML.Element)
+filterChild' p = getCurrentElement
+ >>^ XML.filterChild p
+
+--
+filterChild :: (XML.Element -> Bool)
+ -> FallibleXMLConverter nsID extraState x XML.Element
+filterChild p = filterChild' p
+ >>> maybeToChoice
+
+--
+filterChildName' :: (XML.QName -> Bool)
+ -> XMLConverter nsID extraState x (Maybe XML.Element)
+filterChildName' p = getCurrentElement
+ >>^ XML.filterChildName p
+
+--
+filterChildName :: (XML.QName -> Bool)
+ -> FallibleXMLConverter nsID extraState x XML.Element
+filterChildName p = filterChildName' p
+ >>> maybeToChoice
+
+
+--------------------------------------------------------------------------------
+-- Attributes
+--------------------------------------------------------------------------------
+
+--
+isSet :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> (Either Failure Bool)
+ -> FallibleXMLConverter nsID extraState x Bool
+isSet nsID attrName deflt
+ = findAttr' nsID attrName
+ >>^ maybe deflt stringToBool
+
+--
+isSet' :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> XMLConverter nsID extraState x (Maybe Bool)
+isSet' nsID attrName = findAttr' nsID attrName
+ >>^ (>>= stringToBool')
+
+isSetWithDefault :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> Bool
+ -> XMLConverter nsID extraState x Bool
+isSetWithDefault nsID attrName def'
+ = isSet' nsID attrName
+ >>^ fromMaybe def'
+
+--
+hasAttrValueOf' :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> AttributeValue
+ -> XMLConverter nsID extraState x Bool
+hasAttrValueOf' nsID attrName attrValue
+ = findAttr nsID attrName
+ >>> ( const False ^|||^ (==attrValue))
+
+--
+failIfNotAttrValueOf :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> AttributeValue
+ -> FallibleXMLConverter nsID extraState x ()
+failIfNotAttrValueOf nsID attrName attrValue
+ = hasAttrValueOf' nsID attrName attrValue
+ >>^ boolToChoice
+
+-- | Is the value that is currently transported in the arrow the value of
+-- the specified attribute?
+isThatTheAttrValue :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> FallibleXMLConverter nsID extraState AttributeValue Bool
+isThatTheAttrValue nsID attrName
+ = keepingTheValue
+ (findAttr nsID attrName)
+ >>§ right.(==)
+
+-- | Lookup value in a dictionary, fail if no attribute found or value
+-- not in dictionary
+searchAttrIn :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> [(AttributeValue,a)]
+ -> FallibleXMLConverter nsID extraState x a
+searchAttrIn nsID attrName dict
+ = findAttr nsID attrName
+ >>?^? maybeToChoice.(`lookup` dict )
+
+
+-- | Lookup value in a dictionary. Fail if no attribute found. If value not in
+-- dictionary, return default value
+searchAttrWith :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> a
+ -> [(AttributeValue,a)]
+ -> FallibleXMLConverter nsID extraState x a
+searchAttrWith nsID attrName defV dict
+ = findAttr nsID attrName
+ >>?^ (fromMaybe defV).(`lookup` dict )
+
+-- | Lookup value in a dictionary. If attribute or value not found,
+-- return default value
+searchAttr :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> a
+ -> [(AttributeValue,a)]
+ -> XMLConverter nsID extraState x a
+searchAttr nsID attrName defV dict
+ = searchAttrIn nsID attrName dict
+ >>> const defV ^|||^ id
+
+-- | Read a 'Lookupable' attribute. Fail if no match.
+lookupAttr :: (NameSpaceID nsID, Lookupable a)
+ => nsID -> AttributeName
+ -> FallibleXMLConverter nsID extraState x a
+lookupAttr nsID attrName = lookupAttr' nsID attrName
+ >>^ maybeToChoice
+
+
+-- | Read a 'Lookupable' attribute. Return the result as a 'Maybe'.
+lookupAttr' :: (NameSpaceID nsID, Lookupable a)
+ => nsID -> AttributeName
+ -> XMLConverter nsID extraState x (Maybe a)
+lookupAttr' nsID attrName
+ = findAttr' nsID attrName
+ >>^ (>>= readLookupable)
+
+-- | Read a 'Lookupable' attribute with explicit default
+lookupAttrWithDefault :: (NameSpaceID nsID, Lookupable a)
+ => nsID -> AttributeName
+ -> a
+ -> XMLConverter nsID extraState x a
+lookupAttrWithDefault nsID attrName deflt
+ = lookupAttr' nsID attrName
+ >>^ fromMaybe deflt
+
+-- | Read a 'Lookupable' attribute with implicit default
+lookupDefaultingAttr :: (NameSpaceID nsID, Lookupable a, Default a)
+ => nsID -> AttributeName
+ -> XMLConverter nsID extraState x a
+lookupDefaultingAttr nsID attrName
+ = lookupAttrWithDefault nsID attrName def
+
+-- | Return value as a (Maybe String)
+findAttr' :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> XMLConverter nsID extraState x (Maybe AttributeValue)
+findAttr' nsID attrName = elemName nsID attrName
+ &&& getCurrentElement
+ >>§ XML.findAttr
+
+-- | Return value as string or fail
+findAttr :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> FallibleXMLConverter nsID extraState x AttributeValue
+findAttr nsID attrName = findAttr' nsID attrName
+ >>> maybeToChoice
+
+-- | Return value as string or return provided default value
+findAttrWithDefault :: (NameSpaceID nsID)
+ => nsID -> AttributeName
+ -> AttributeValue
+ -> XMLConverter nsID extraState x AttributeValue
+findAttrWithDefault nsID attrName deflt
+ = findAttr' nsID attrName
+ >>^ fromMaybe deflt
+
+-- | Read and return value or fail
+readAttr :: (NameSpaceID nsID, Read attrValue)
+ => nsID -> AttributeName
+ -> FallibleXMLConverter nsID extraState x attrValue
+readAttr nsID attrName = readAttr' nsID attrName
+ >>> maybeToChoice
+
+-- | Read and return value or return Nothing
+readAttr' :: (NameSpaceID nsID, Read attrValue)
+ => nsID -> AttributeName
+ -> XMLConverter nsID extraState x (Maybe attrValue)
+readAttr' nsID attrName = findAttr' nsID attrName
+ >>^ (>>= tryToRead)
+
+-- | Read and return value or return provided default value
+readAttrWithDefault :: (NameSpaceID nsID, Read attrValue)
+ => nsID -> AttributeName
+ -> attrValue
+ -> XMLConverter nsID extraState x attrValue
+readAttrWithDefault nsID attrName deflt
+ = findAttr' nsID attrName
+ >>^ (>>= tryToRead)
+ >>^ fromMaybe deflt
+
+-- | Read and return value or return default value from 'Default' instance
+getAttr :: (NameSpaceID nsID, Read attrValue, Default attrValue)
+ => nsID -> AttributeName
+ -> XMLConverter nsID extraState x attrValue
+getAttr nsID attrName = readAttrWithDefault nsID attrName def
+
+--------------------------------------------------------------------------------
+-- Movements
+--------------------------------------------------------------------------------
+
+--
+jumpThere :: XMLConverter nsID extraState XML.Element XML.Element
+jumpThere = withState (\state element
+ -> ( pushElement element state , element )
+ )
+
+--
+swapStack :: XMLConverter nsID extraState [XML.Element] [XML.Element]
+swapStack = withState swapStack'
+
+--
+jumpBack :: FallibleXMLConverter nsID extraState _x _x
+jumpBack = tryModifyState (popElement >>> maybeToChoice)
+
+-- | Support function for "procedural" converters: jump to an element, execute
+-- a converter, jump back.
+-- This version is safer than 'executeThere', because it does not rely on the
+-- internal stack. As a result, the converter can not move around in arbitrary
+-- ways. The downside is of course that some of the environment is not
+-- accessible to the converter.
+switchingTheStack :: XMLConverter nsID moreState a b
+ -> XMLConverter nsID moreState (a, XML.Element) b
+switchingTheStack a = second ( (:[]) ^>> swapStack )
+ >>> first a
+ >>> second swapStack
+ >>^ fst
+
+-- | Support function for "procedural" converters: jumps to an element, executes
+-- a converter, jumps back.
+-- Make sure that the converter is well-behaved; that is it should
+-- return to the exact position it started from in /every possible path/ of
+-- execution, even if it "fails". If it does not, you may encounter
+-- strange bugs. If you are not sure about the behaviour or want to use
+-- shortcuts, you can often use 'switchingTheStack' instead.
+executeThere :: FallibleXMLConverter nsID moreState a b
+ -> FallibleXMLConverter nsID moreState (a, XML.Element) b
+executeThere a = second jumpThere
+ >>> fst
+ ^>> a
+ >>> jumpBack -- >>? jumpBack would not ensure the jump.
+ >>^ collapseEither
+
+-- | Do something in a sub-element, tnen come back
+executeIn :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState f s
+ -> FallibleXMLConverter nsID extraState f s
+executeIn nsID name a = keepingTheValue
+ (findChild nsID name)
+ >>> ignoringState liftFailure
+ >>? switchingTheStack a
+ where liftFailure (_, (Left f)) = Left f
+ liftFailure (x, (Right e)) = Right (x, e)
+
+--------------------------------------------------------------------------------
+-- Iterating over children
+--------------------------------------------------------------------------------
+
+-- Helper converter to prepare different types of iterations.
+-- It lifts the children (of a certain type) of the current element
+-- into the value level and pairs each one with the current input value.
+prepareIteration :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> XMLConverter nsID extraState b [(b, XML.Element)]
+prepareIteration nsID name = keepingTheValue
+ (findChildren nsID name)
+ >>§ distributeValue
+
+-- | Applies a converter to every child element of a specific type.
+-- Collects results in a 'Monoid'.
+-- Fails completely if any conversion fails.
+collectEvery :: (NameSpaceID nsID, Monoid m)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState a m
+ -> FallibleXMLConverter nsID extraState a m
+collectEvery nsID name a = prepareIteration nsID name
+ >>> foldS' (switchingTheStack a)
+
+--
+withEveryL :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState a b
+ -> FallibleXMLConverter nsID extraState a [b]
+withEveryL = withEvery
+
+-- | Applies a converter to every child element of a specific type.
+-- Collects results in a 'MonadPlus'.
+-- Fails completely if any conversion fails.
+withEvery :: (NameSpaceID nsID, MonadPlus m)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState a b
+ -> FallibleXMLConverter nsID extraState a (m b)
+withEvery nsID name a = prepareIteration nsID name
+ >>> iterateS' (switchingTheStack a)
+
+-- | Applies a converter to every child element of a specific type.
+-- Collects all successful results in a list.
+tryAll :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState b a
+ -> XMLConverter nsID extraState b [a]
+tryAll nsID name a = prepareIteration nsID name
+ >>> iterateS (switchingTheStack a)
+ >>^ collectRights
+
+-- | Applies a converter to every child element of a specific type.
+-- Collects all successful results.
+tryAll' :: (NameSpaceID nsID, F.Foldable c, MonadPlus c)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState b a
+ -> XMLConverter nsID extraState b (c a)
+tryAll' nsID name a = prepareIteration nsID name
+ >>> iterateS (switchingTheStack a)
+ >>^ collectRightsF
+
+--------------------------------------------------------------------------------
+-- Matching children
+--------------------------------------------------------------------------------
+
+type IdXMLConverter nsID moreState x
+ = XMLConverter nsID moreState x x
+
+type MaybeEConverter nsID moreState x
+ = Maybe (IdXMLConverter nsID moreState (x, XML.Element))
+
+-- Chainable converter that helps deciding which converter to actually use.
+type ElementMatchConverter nsID extraState x
+ = IdXMLConverter nsID
+ extraState
+ (MaybeEConverter nsID extraState x, XML.Element)
+
+type MaybeCConverter nsID moreState x
+ = Maybe (IdXMLConverter nsID moreState (x, XML.Content))
+
+-- Chainable converter that helps deciding which converter to actually use.
+type ContentMatchConverter nsID extraState x
+ = IdXMLConverter nsID
+ extraState
+ (MaybeCConverter nsID extraState x, XML.Content)
+
+-- Helper function: The @c@ is actually a converter that is to be selected by
+-- matching XML elements to the first two parameters.
+-- The fold used to match elements however is very simple, so to use it,
+-- this function wraps the converter in another converter that unifies
+-- the accumulator. Think of a lot of converters with the resulting type
+-- chained together. The accumulator not only transports the element
+-- unchanged to the next matcher, it also does the actual selecting by
+-- combining the intermediate results with '(<|>)'.
+makeMatcherE :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState a a
+ -> ElementMatchConverter nsID extraState a
+makeMatcherE nsID name c = ( second (
+ elemNameIs nsID name
+ >>^ bool Nothing (Just tryC)
+ )
+ >>§ (<|>)
+ ) &&&^ snd
+ where tryC = (fst ^&&& executeThere c >>§ recover) &&&^ snd
+
+-- Helper function: The @c@ is actually a converter that is to be selected by
+-- matching XML content to the first two parameters.
+-- The fold used to match elements however is very simple, so to use it,
+-- this function wraps the converter in another converter that unifies
+-- the accumulator. Think of a lot of converters with the resulting type
+-- chained together. The accumulator not only transports the element
+-- unchanged to the next matcher, it also does the actual selecting by
+-- combining the intermediate results with '(<|>)'.
+makeMatcherC :: (NameSpaceID nsID)
+ => nsID -> ElementName
+ -> FallibleXMLConverter nsID extraState a a
+ -> ContentMatchConverter nsID extraState a
+makeMatcherC nsID name c = ( second ( contentToElem
+ >>> returnV Nothing
+ ||| ( elemNameIs nsID name
+ >>^ bool Nothing (Just cWithJump)
+ )
+ )
+ >>§ (<|>)
+ ) &&&^ snd
+ where cWithJump = ( fst
+ ^&&& ( second contentToElem
+ >>> spreadChoice
+ ^>>? executeThere c
+ )
+ >>§ recover)
+ &&&^ snd
+ contentToElem :: FallibleXMLConverter nsID extraState XML.Content XML.Element
+ contentToElem = arr $ \e -> case e of
+ XML.Elem e' -> succeedWith e'
+ _ -> failEmpty
+
+-- Creates and chains a bunch of matchers
+prepareMatchersE :: (NameSpaceID nsID)
+ => [(nsID, ElementName, FallibleXMLConverter nsID extraState x x)]
+ -> ElementMatchConverter nsID extraState x
+--prepareMatchersE = foldSs . (map $ uncurry3 makeMatcherE)
+prepareMatchersE = reverseComposition . (map $ uncurry3 makeMatcherE)
+
+-- Creates and chains a bunch of matchers
+prepareMatchersC :: (NameSpaceID nsID)
+ => [(nsID, ElementName, FallibleXMLConverter nsID extraState x x)]
+ -> ContentMatchConverter nsID extraState x
+--prepareMatchersC = foldSs . (map $ uncurry3 makeMatcherC)
+prepareMatchersC = reverseComposition . (map $ uncurry3 makeMatcherC)
+
+-- | Takes a list of element-data - converter groups and
+-- * Finds all children of the current element
+-- * Matches each group to each child in order (at most one group per child)
+-- * Filters non-matched children
+-- * Chains all found converters in child-order
+-- * Applies the chain to the input element
+matchChildren :: (NameSpaceID nsID)
+ => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]
+ -> XMLConverter nsID extraState a a
+matchChildren lookups = let matcher = prepareMatchersE lookups
+ in keepingTheValue (
+ elChildren
+ >>> map (Nothing,)
+ ^>> iterateSL matcher
+ >>^ catMaybes.map (\(m,e) -> fmap (swallowElem e) m)
+ -- >>> foldSs
+ >>> reverseComposition
+ )
+ >>> swap
+ ^>> app
+ where
+ -- let the converter swallow the element and drop the element
+ -- in the return value
+ swallowElem element converter = (,element) ^>> converter >>^ fst
+
+--
+matchContent'' :: (NameSpaceID nsID)
+ => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]
+ -> XMLConverter nsID extraState a a
+matchContent'' lookups = let matcher = prepareMatchersC lookups
+ in keepingTheValue (
+ elContent
+ >>> map (Nothing,)
+ ^>> iterateSL matcher
+ >>^ catMaybes.map (\(m,c) -> fmap (swallowContent c) m)
+ -- >>> foldSs
+ >>> reverseComposition
+ )
+ >>> swap
+ ^>> app
+ where
+ -- let the converter swallow the content and drop the content
+ -- in the return value
+ swallowContent content converter = (,content) ^>> converter >>^ fst
+
+
+-- | Takes a list of element-data - converter groups and
+-- * Finds all content of the current element
+-- * Matches each group to each piece of content in order
+-- (at most one group per piece of content)
+-- * Filters non-matched content
+-- * Chains all found converters in content-order
+-- * Applies the chain to the input element
+matchContent' :: (NameSpaceID nsID)
+ => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]
+ -> XMLConverter nsID extraState a a
+matchContent' lookups = matchContent lookups (arr fst)
+
+-- | Takes a list of element-data - converter groups and
+-- * Finds all content of the current element
+-- * Matches each group to each piece of content in order
+-- (at most one group per piece of content)
+-- * Adds a default converter for all non-matched content
+-- * Chains all found converters in content-order
+-- * Applies the chain to the input element
+matchContent :: (NameSpaceID nsID)
+ => [(nsID, ElementName, FallibleXMLConverter nsID extraState a a)]
+ -> XMLConverter nsID extraState (a,XML.Content) a
+ -> XMLConverter nsID extraState a a
+matchContent lookups fallback
+ = let matcher = prepareMatchersC lookups
+ in keepingTheValue (
+ elContent
+ >>> map (Nothing,)
+ ^>> iterateSL matcher
+ >>^ map swallowOrFallback
+ -- >>> foldSs
+ >>> reverseComposition
+ )
+ >>> swap
+ ^>> app
+ where
+ -- let the converter swallow the content and drop the content
+ -- in the return value
+ swallowOrFallback (Just converter,content) = (,content) ^>> converter >>^ fst
+ swallowOrFallback (Nothing ,content) = (,content) ^>> fallback
+
+--------------------------------------------------------------------------------
+-- Internals
+--------------------------------------------------------------------------------
+
+stringToBool :: (Monoid failure) => String -> Either failure Bool
+stringToBool val -- stringToBool' val >>> maybeToChoice
+ | val `elem` trueValues = succeedWith True
+ | val `elem` falseValues = succeedWith False
+ | otherwise = failEmpty
+ where trueValues = ["true" ,"on" ,"1"]
+ falseValues = ["false","off","0"]
+
+stringToBool' :: String -> Maybe Bool
+stringToBool' val | val `elem` trueValues = Just True
+ | val `elem` falseValues = Just False
+ | otherwise = Nothing
+ where trueValues = ["true" ,"on" ,"1"]
+ falseValues = ["false","off","0"]
+
+
+distributeValue :: a -> [b] -> [(a,b)]
+distributeValue = map.(,)
+
+--------------------------------------------------------------------------------
+
+{-
+NOTES
+It might be a good idea to refactor the namespace stuff.
+E.g.: if a namespace constructor took a string as a parameter, things like
+> a ?>/< (NsText,"body")
+would be nicer.
+Together with a rename and some trickery, something like
+> |< NsText "body" >< NsText "p" ?> a </> </>|
+might even be possible.
+
+Some day, XML.Light should be replaced by something better.
+While doing that, it might be useful to replace String as the type of element
+names with something else, too. (Of course with OverloadedStrings).
+While doing that, maybe the types can be created in a way that something like
+> NsText:"body"
+could be used. Overloading (:) does not sounds like the best idea, but if the
+element name type was a list, this might be possible.
+Of course that would be a bit hackish, so the "right" way would probably be
+something like
+> InNS NsText "body"
+but isn't that a bit boring? ;)
+-}