packages feed

hxt-8.4.0: examples/arrows/performance/GenDoc.hs

{-# LANGUAGE BangPatterns#-}

-- ----------------------------------------

module Main
where

import Text.XML.HXT.Arrow

import Text.XML.HXT.DOM.Unicode
    ( unicodeToXmlEntity
    )

import Control.Monad.State.Strict hiding (when)

import Data.Maybe

import System.IO			-- import the IO and commandline option stuff
import System.Environment

-- ----------------------------------------

main	:: IO ()
main
    = do
      p <- getProgName
      (is : _) <- getArgs
      let i = (read is)::Int
      main' p i
    where
    main' p' = fromMaybe main0 . lookup (pn p') $ mpt
    mpt = [ ("GenDoc",	   main1)
	  , ("ReadDoc",    main2)
	  , ("PruneRight", main3 False)
	  , ("PruneLeft",  main3 True)
	  , ("MemTest",    main4)
	 ]

-- ----------------------------------------

-- generate a document containing a binary tree of 2^i leafs (= 2^(i-1) XML elements)

main1	:: Int -> IO ()
main1 i
    = runX (genDoc i (fn i))
      >> return ()

-- ----------------------------------------

-- read a document containing a binary tree of 2^i leafs

main2	:: Int -> IO ()
main2 i
    = do
      [x] <- runX (readDoc (fn i)
		   >>>
		   traceMsg 1 "start unpickle"
		   >>>
		   unpickleTree
		   >>>
		   traceMsg 1 "start fold"
		   >>>
		   arr (foldT1 max)
		  )
      putStrLn ( "maximum value in tree is " ++ show x ++
		 ", expected value was " ++ show ((2::Int)^i)
	       )

-- ----------------------------------------

-- test on lazyness, is the whole tree read or only the first child of every child node?

main3	:: Bool -> Int -> IO ()
main3 l i
    = do
      [t] <- runX ( readDoc (fn i)
		    >>>
		    fromLA (xshow ( getChildren
				    >>>
				    if l then pruneForkLeft else pruneForkRight
				  )
			   )
		  )
      putStrLn ("pruned binary tree is : " ++ show t)

-- ----------------------------------------

main4	:: Int -> IO ()
main4 i
    = do
      [x] <- runX ( setTraceLevel 1
		    >>>
		    traceMsg 1 ("generate tree of depth " ++ show i)
		    >>>
		    fromLA (genTree 0 i)
		    >>>
		    traceMsg 1 ("compute maximum and minimum")
		    >>>
		    fromLA ( foldBTree maximum &&& foldBTree minimum )	-- 2 traversals: complete tree in mem
		    >>>
		    arr2 (\ ma mi -> "maximum value = " ++ show ma ++ ", minimum = " ++ show mi )
		    >>>
		    traceValue 1 id
		  )
      putStrLn x

foldBTree	:: ([Int] -> Int) -> LA XmlTree Int
foldBTree f
    = choiceA
      [ hasName "leaf" :-> ( getAttrValue "value" >>^ read )
      , hasName "fork" :-> ( (getChildren >>> foldBTree f) >. f )
      , this           :-> none
      ]

-- ----------------------------------------

-- just to check how much memory is used for the tree

main0	:: Int -> IO ()
main0 i
    = do
      let t = mkBTree i
      let m = show . foldT1 max $ t
      putStrLn ("maximum value in tree is " ++ m ++ ", minimum value is " ++ show (foldT1 min t))
      return ()

-- ----------------------------------------

pn	:: String -> String
pn	= reverse . takeWhile (/= '/') . reverse

fn	:: Int -> String
fn	= ("tree-" ++) . (++ ".xml") . reverse . take 4 . reverse . ((replicate 4 '0') ++ ) . show

-- ----------------------------------------

genTree	:: Int -> Int -> LA XmlTree XmlTree
genTree !n !d
    | d == 0	= aelem "leaf" [sattr "value" (show (n + 1))]
    | otherwise	= selem "fork" [ genTree (2*n)   (d-1)
			       , genTree (2*n+1) (d-1)
			       ]

-- ----------------------------------------

genDoc		:: Int -> String -> IOSArrow b XmlTree
genDoc d out    = constA (mkBTree d)
		  >>>
		  xpickleVal xpickle
		  >>>
		  putDoc out

-- ----------------------------------------

readDoc	:: String -> IOSArrow b XmlTree
readDoc src
    = readDocument [ (a_tagsoup, v_1)
		   , (a_parse_xml, v_1)
		   , (a_remove_whitespace, v_1)
		   , (a_encoding, isoLatin1)
		   , (a_issue_warnings, v_0)
		   , (a_trace, v_1)
		   , (a_strict_input, v_0)
		   ] src
      >>>
      processChildren (isElem `guards` this)

-- ----------------------------------------

unpickleTree	:: ArrowXml a => a XmlTree BTree
unpickleTree	= xunpickleVal xpickle

-- ----------------------------------------

pruneForkRight	:: LA XmlTree XmlTree
pruneForkRight
    = ( replaceChildren
	( ( getChildren >>. take 1 )
	  >>>
	  pruneForkRight
	)
      ) `when` (hasName "fork")


pruneForkLeft	:: LA XmlTree XmlTree
pruneForkLeft
    = ( replaceChildren
	( ( getChildren >>. drop 1 )
	  >>>
	  pruneForkLeft
	)
      ) `when` (hasName "fork")

-- ----------------------------------------

type Counter a	= State Int a

incr	:: Counter Int
incr	= do
	  modify (+1)
	  get

-- ----------------------------------------

data BTree	= Leaf Int
		| Fork BTree BTree
		  deriving (Show)

instance XmlPickler BTree where
    xpickle = xpAlt tag ps
	where
	tag (Leaf _	) = 0
	tag (Fork _ _	) = 1
	ps = [ xpWrap ( Leaf, \ (Leaf i) -> i)
	       ( xpElem "leaf" $ xpAttr "value" $ xpickle )

	     , xpWrap ( uncurry Fork, \ (Fork l r) -> (l, r))
	       ( xpElem "fork" $ xpPair xpickle xpickle )
	       ]

-- ----------------------------------------

mkBTree		:: Int -> BTree
mkBTree	depth	= evalState (mkT depth) 0

mkT	:: Int -> Counter BTree
mkT 0	= do
	  i <- incr
	  return (Leaf i)
mkT n	= do
	  l <- mkT (n-1)
	  r <- mkT (n-1)
	  return (Fork l r)

-- ----------------------------------------

foldT1	:: (Int -> Int -> Int) -> BTree -> Int
foldT1 _  (Leaf v)	= v
foldT1 op (Fork l r)	= foldT1 op l `op` foldT1 op r

-- ----------------------------------------

-- output is done with low level ops to write the
-- document i a lazy manner
-- adding an xml pi and encoding is done "by hand"
-- latin1 decoding is the identity, so please generate the
-- docs with latin1 encoding. Here ist done even with ASCCI
-- every none ASCII char is represented by a char ref (&nnn;)

putDoc	:: String -> IOStateArrow s XmlTree XmlTree
putDoc dst
    = addXmlPi
      >>>
      addXmlPiEncoding isoLatin1
      >>>
      xshow getChildren
      >>>
      arr unicodeToXmlEntity
      >>>
      arrIO (\ s -> hPutDocument (\h -> hPutStrLn h s))
      >>>
      none
      where
      isStdout	= null dst || dst == "-"

      hPutDocument	:: (Handle -> IO()) -> IO()
      hPutDocument action
	  | isStdout
	      = action stdout
	  | otherwise
	      = do
		handle <- openBinaryFile dst WriteMode
		action handle
		hClose handle

-- ----------------------------------------