{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} import Control.Monad (guard) import Control.Monad.IO.Class (liftIO) import Data.Char (chr,ord) import Data.String (fromString) import Data.Text (toLower) import Data.XML.Types import Test.HUnit hiding (Test) import Test.Hspec import Test.Hspec.HUnit import Text.XML.Enumerator.Parse (decodeEntities) import qualified Control.Exception as C import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.Map as Map import qualified Text.XML.Enumerator.Document as D import qualified Text.XML.Enumerator.Parse as P import Text.XML.Enumerator.Parse (decodeEntities) import qualified Text.XML.Enumerator.Parse as P import qualified Text.XML.Enumerator.Render as R import qualified Data.Map as Map import qualified Data.ByteString.Lazy.Char8 as L import Control.Monad.IO.Class (liftIO) import qualified Data.Enumerator as E import Data.Enumerator(($$)) import qualified Data.Enumerator.List as EL import Data.Monoid import Data.Text(Text) import Control.Monad.IO.Class(MonadIO) import Control.Monad import Control.Applicative((<$>), (<*>)) main :: IO () main = hspec $ fmap concat $ sequence [t0, t1] t0 = describe "XML parsing and rendering" [ it "is idempotent to parse and render a document" documentParseRender , it "has valid parser combinators" combinators , it "has working ignoreSiblings function" testIgnoreSiblings , it "has working IgnoreElem function" testIgnoreElem , it "has working skipTill function" testSkipTill , it "has working choose function" testChoose , it "has working many function" testMany , it "has working orE" testOrE , it "has working chooseSplit" testChooseSplit , it "has working permute" testPermute , it "has working permuteFallback" testPermuteFallback , it "has working tags" testTags , it "has working tagsPermute" testTagsPermute ] t1 = fmap concat $ sequence [ describe "Checking dependency of chunk size for iteratees" [ it "doesn't depend for ignoreSiblings" testSib , it "doesn't depend for ignoreElem" testElem , it "doesn't depend for skipTill" testSkipTill , it "doesn't depend for tag" testTag ], describe "Process nested iteratees. (Launch missiles?..)" [ it "can run nested iteratees for siblings. E.g. to render text" testProcSib , it "can run nested iteratees for next element" testProcElem ] ] where testSib = testI P.ignoreSiblings $ drop 2 testData testElem = testI P.ignoreElem $ drop 2 testData testSkipTill = testI (P.skipTill $ P.tagNoAttr "E2" P.ignoreSiblings) $ drop 1 testData testTag = testI (P.tagNoAttr "root" $ P.skipTill $ P.tagNoAttr "E2" P.contentMaybe) testData testProcSib = join $ fmap (@?=("", [EventEndElement "root"])) $ resI (P.processSiblings renderTextI) (drop 1 testData) 1 testProcElem = join $ fmap (@?=(Just "", dropWhile (/=EventBeginElement "E2" mempty) testData)) $ resI (P.processElem renderTextI) (drop 1 testData) 1 renderTextI = E.joinI $ R.renderText $$ E.foldl' mappend mempty testData = [ EventBeginElement "root" mempty , EventBeginElement "E0" mempty , EventBeginElement "E1" mempty , EventBeginElement "E11" mempty , EventEndElement "E11" , EventBeginElement "E12" mempty , EventEndElement "E12" , EventEndElement "E1" , EventBeginElement "E3" mempty , EventEndElement "E3" , EventEndElement "E0" , EventBeginElement "E2" mempty , EventEndElement "E2" , EventEndElement "root" ] resI i xs n = E.run_ $ E.enumList n xs $$ (i >>= \r-> EL.consume >>= \c -> return (r,c)) cmpI i xs n = liftM2 (==) (resI i xs n) (resI i xs $ n+1) testI :: (Eq t, Eq a) => E.Iteratee a IO t -> [a] -> IO () testI i xs = assert $ fmap and $ mapM (cmpI i xs) [1..20] documentParseRender = mapM_ go docs where go x = x @=? D.parseLBS_ (D.renderLBS x) decodeEntities docs = [ Document (Prologue [] Nothing []) (Element "foo" Map.empty []) [] , D.parseLBS_ "\n\n" decodeEntities , D.parseLBS_ "\n\n&ignore;" decodeEntities , D.parseLBS_ "]]>" decodeEntities , D.parseLBS_ "" decodeEntities , D.parseLBS_ "" decodeEntities , D.parseLBS_ "" decodeEntities ] combinators = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagName "hello" (P.requireAttr "world") $ \world -> do liftIO $ world @?= "true" P.force "need child1" $ P.tagNoAttr "{mynamespace}child1" $ return () P.force "need child2" $ P.tagNoAttr "child2" $ return () P.force "need child3" $ P.tagNoAttr "child3" $ do x <- P.contentMaybe liftIO $ x @?= Just "combine &content" where input = L.concat [ "\n" , "\n" , "" , "" , "" , "" , " " , "combine <all> \n" , "" ] testIgnoreSiblings = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do P.ignoreSiblings return () testIgnoreElem = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do P.ignoreElem P.ignoreElem return () testSkipTill = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do P.skipTill (P.tagNoAttr "ignore" P.ignoreSiblings) return () -- where input = L.concat [ "\n" , "\n" , "" , "" , "" , "" , "" , "" , "\n" , "" ] testChoose = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do x <- P.choose [ P.tagNoAttr "failure" $ return 1 , P.tagNoAttr "success" $ return 2 ] liftIO $ x @?= Just 2 where input = L.concat [ "\n" , "\n" , "" , "" , "" ] testMany = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do x <- P.many $ P.tagNoAttr "success" $ return () liftIO $ length x @?= 5 where input = L.concat [ "\n" , "\n" , "" , "" , "" , "" , "" , "" , "" ] testOrE = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do x <- P.tagNoAttr "failure" (return 1) `P.orE` P.tagNoAttr "success" (return 2) liftIO $ x @?= Just 2 where input = L.concat [ "\n" , "\n" , "" , "" , "" ] testChooseSplit = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do x <- P.chooseSplit (\t-> P.tagNoAttr t (return t)) ["a", "b", "c"] liftIO $ x @?= Just ("b",["a","c"]) where input = L.concat [ "\n" , "\n" , "" , "" , "" ] testPermute = do let frame input = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ P.permute (\t -> P.tagNoAttr t (return t)) ["a", "b"] frame input1 >>= \result1 -> result1 @?= Just ["a", "b"] frame input2 >>= \result2 -> result2 @?= Just ["b", "a"] frame input3 >>= \result3 -> result3 @?= Nothing C.try (frame input4) >>= \result4 -> case result4 of Left (P.XmlException { P.xmlBadInput = Just (EventBeginElement Name { nameLocalName = "c" , nameNamespace = Nothing , namePrefix = Nothing } _) }) -> return () -- right type of error Left _ -> assertFailure "wrong error" Right _ -> assertFailure "erroneous document requires an error" where input1 = L.concat [ "\n" , "\n" , "" , "" , "" , "" ] input2 = L.concat [ "\n" , "\n" , "" , "" , "" , "" ] input3 = L.concat [ "\n" , "\n" , "" , "" , "" ] input4 = L.concat [ "\n" , "\n" , "" , "" , "" , "" ] testPermuteFallback = do let frame input = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ P.permuteFallback (fmap return `fmap` P.contentMaybe) (\t -> P.tagNoAttr t (return $ nameLocalName t)) ["a", "b"] frame input1 >>= \result1 -> result1 @?= Just ["a", "t", "b"] frame input2 >>= \result2 -> result2 @?= Just ["t", "b", "a"] frame input3 >>= \result3 -> result3 @?= Nothing C.try (frame input4) >>= \result4 -> case result4 of Left (P.XmlException { P.xmlBadInput = Just (EventBeginElement Name { nameLocalName = "c" , nameNamespace = Nothing , namePrefix = Nothing } _) }) -> return () -- right type of error Left _ -> assertFailure "wrong error" Right _ -> assertFailure "erroneous document requires an error" where input1 = L.concat [ "\n" , "\n" , "" , "" , "t" , "" , "" ] input2 = L.concat [ "\n" , "\n" , "" , "t" , "" , "" , "" ] input3 = L.concat [ "\n" , "\n" , "" , "" , "" ] input4 = L.concat [ "\n" , "\n" , "" , "" , "" , "" ] testTags = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do x <- P.tags (\state name -> do let n = nameLocalName name guard (n == fromString [chr $ ord 'a' + state]) Just (return (), \_ -> return (state + 1, Just n))) (const $ return Nothing) 0 liftIO $ x @?= (5, ["a", "b", "c", "d", "e"]) where input = L.concat [ "\n" , "\n" , "" , "" , "" , "" , "" , "" , "" ] testTagsPermute = P.parseLBS_ input decodeEntities $ do P.force "need hello" $ P.tagNoAttr "hello" $ do let p c = (return (), \_ -> return (Just c)) x <- P.tagsPermute (toLower . nameLocalName) (Map.fromList $ map (\c -> (c, p c)) ["a", "b", "c", "d", "e"]) (return Nothing) liftIO $ x @?= Just ["d", "b", "e", "a", "c"] where input = L.concat [ "\n" , "\n" , "" , "" , "" , "" , "" , "" , "" ]