----------------------------------------------------------------------------
--
-- Module	: XMLCombinators
-- Copyright	: (C) 2001,2002 Joe English.  Freely redistributable.
-- License	: "MIT-style"
--
-- Author	: Joe English <jenglish@flightlab.com>
-- Stability	: experimental
-- Portability	: portable
--
-- $Id: XMLCombinators.hs,v 1.3 2002/03/06 04:20:52 joe Exp $
--
----------------------------------------------------------------------------
--
-- 2 Nov 2001; revised 5 Nov 2001 to use Arrows.
--

module XMLCombinators
    ( module Arrow, module Filter, module HXML
    , xmlFilterMain, testFilter
    , qSelf, qChildren, qDescendants, qSubtree
    , qElem, qElems, qText, qEntity
    , qAttval, qAttlist
    , mkElem, mkElement
    , mkText, mkLiteral
    , mkAtt
    , apChildren, keepText
    , qInner, qOuter, qFirst
    , qMatch, qTest
    , aFoldTree, aScanTree
    , (.//), (./), (|>|), (|?>), qChildElem
    )
    where

import Monad
import Arrow
import Filter
import HXML

infixr 2 |?>		-- same as ?>, :>
infixl 1 ./, .//	-- same as >>>

--
-- Predicate adapters:
--

qMatch :: Filter a b -> Filter a a
qMatch f = aGuard (not . null . runFilter f)

qTest :: Filter a b -> Filter a Bool
qTest f = makeFilter (return . not . null . runFilter f)

(|?>) :: Filter a x -> Choice (Filter a b) -> Filter a b
f |?> g = (not . null . runFilter f) ?> g

--
-- Constructors:
--

mkNode :: a -> Filter s (Tree a) -> Filter s (Tree a)
mkNode nd body = makeFilter (\x -> [Tree nd (runFilter body x)])

mkLiteral :: String -> Filter a XML
mkLiteral = aConst . leafNode . TXNode

mkElem :: Name -> Filter a XML -> Filter a XML
mkElem name = mkNode (ELNode name [])

mkElement :: Name -> Filter a (Name,String) -> Filter a XML -> Filter a XML
mkElement name atts body = arr $ \x ->
	Tree (ELNode name (runFilter atts x)) (runFilter body x)

mkAtt :: Name -> Filter a String -> Filter a (Name,String)
mkAtt name val = aConst name &&& val

mkText :: Filter String XML
mkText = arr (leafNode . TXNode)

--
-- Navigation:
--

qChildren, qDescendants, qSubtree :: Filter (Tree a) (Tree a)
qChildren	= makeFilter treeChildren
qSubtree	= qSelf +++ (qChildren >>> qSubtree)
qDescendants	= qChildren >>> qSubtree

qInner, qOuter :: Filter (Tree a) b -> Filter (Tree a) b
qOuter p = p |>| (qChildren >>> qOuter p)
qInner p = (qChildren >>> qInner p) |>| p


(|>|) :: Filter a b -> Filter a b -> Filter a b
f |>| g = makeFilter choose where
    choose x = let fx = runFilter f x in if null fx then runFilter g x else fx

qFirst :: Filter a b -> Filter a b
qFirst = apFilter take1 where
	take1 [] 	= []
	take1 (x:_)	= [x]

--
-- Editing:
--

apChildren :: Filter (Tree a) (Tree a) -> Filter (Tree a) (Tree a)
apChildren f = arr (\(Tree x ts) -> Tree x (ts >>= runFilter f))

aFoldTree, aScanTree :: Filter (Tree a) (Tree a) -> Filter (Tree a) (Tree a)

aFoldTree f = apChildren (aFoldTree f) >>> f
aScanTree f = f >>> apChildren (aScanTree f)

--
-- Predicates:
--

qElem :: Name -> Filter XML XML
qElem name = aGuard (q . treeRoot) where
    q (ELNode gi _)	= gi == name
    q _			= False

qElems :: [Name] -> Filter XML XML	-- =  foldr1 (|>|) . map qElem
qElems gis = aGuard (test . treeRoot) where
    test (ELNode gi _)	= elem gi gis
    test _		= False

qText :: Filter XML XML
qText = aGuard (isTXNode . treeRoot) where
    isTXNode (TXNode _)	= True
    isTXNode _		= False

qEntity :: Filter XML XML
qEntity = aGuard (isENNode . treeRoot) where
    isENNode (ENNode _)	= True
    isENNode _		= False

qSelf :: Filter x x
qSelf = arr id

--
-- Extractions:
--

keepText :: Filter XML XML
keepText = qSubtree >>> (qText |>| qEntity)
-- ALT: arr stringValue >>> mkText

qAttval :: Name -> Filter XML String
qAttval name = arr (attval name) >>> aMaybe

qAttlist :: Filter XML (Name,String)
qAttlist = makeFilter attributes

--
-- Shorthand:
--

(./), (.//) :: Filter a XML -> String -> Filter a XML
f ./  gi = f >>> qChildren >>> qElem gi
f .// gi = f >>> qOuter (qElem gi)

qChildElem :: String -> Filter XML XML
qChildElem gi = qFirst (qChildren >>> qElem gi)

--
-- Utilities:
--

xmlFilterMain :: Filter XML XML -> IO ()
xmlFilterMain f =
    getContents >>= mapM_ printXML . runFilter f . parseXML
    >> putChar '\n'

testFilter :: FilePath -> Filter XML XML -> IO ()
testFilter filename f =
    readFile filename >>= mapM_ printXML . runFilter f . parseXML

-- *EOF*
