@@ -0,0 +1,290 @@
1+module Main where
2+
3+import Control.Exception
4+import Control.Lens
5+import Control.Monad
6+import Data.Default
7+import Data.List.NonEmpty (NonEmpty(..))
8+import qualified Data.List.NonEmpty as NE
9+import Data.Monoid
10+import Data.Text (Text)
11+import Test.Hspec
12+import Text.Shakespeare.Text (lt)
13+import Text.XML
14+import Text.XML.DOM.Parser
15+
16+
17+data TestStructure = TestStructure
18+ { tsName :: Text
19+ , tsInts :: [Int]
20+ , tsBools :: NonEmpty Bool
21+ } deriving (Eq, Show)
22+
23+testStructureFromDom :: (Monad m) => DomParserT Identity m TestStructure
24+testStructureFromDom = do
25+ tsName <- inElem "name" fromDom
26+ tsInts <- inElemAll "int" intFromDom
27+ tsBools <- inElemNe "bool" boolFromDom
28+ return TestStructure{..}
29+
30+instance FromDom TestStructure where
31+ fromDom = testStructureFromDom
32+
33+docStruct :: Document
34+docStruct = parseText_ def [lt|
35+<?xml version="1.0" encoding="utf-8"?>
36+<root>
37+ <name>name</name>
38+ <int>1</int>
39+ <int>2</int>
40+ <bool>t</bool>
41+</root>
42+|]
43+
44+docNone :: Document
45+docNone = parseText_ def [lt|
46+<?xml version="1.0" encoding="utf-8"?>
47+<root>
48+</root>
49+|]
50+
51+docEmpty :: Document
52+docEmpty = parseText_ def [lt|
53+<?xml version="1.0" encoding="utf-8"?>
54+<root>
55+ <a/>
56+</root>
57+|]
58+
59+docSimple :: Document
60+docSimple = parseText_ def [lt|
61+<?xml version="1.0" encoding="utf-8"?>
62+<root>
63+ <a>content</a>
64+</root>
65+|]
66+
67+docMultiple :: Document
68+docMultiple = parseText_ def [lt|
69+<?xml version="1.0" encoding="utf-8"?>
70+<root>
71+ <a>content1</a>
72+ <a>content2</a>
73+ <a>content3</a>
74+</root>
75+|]
76+
77+docDeep :: Document
78+docDeep = parseText_ def [lt|
79+<?xml version="1.0" encoding="utf-8"?>
80+<root>
81+ <a><b>content</b></a>
82+</root>
83+|]
84+
85+docDeepWithContent :: Document
86+docDeepWithContent = parseText_ def [lt|
87+<?xml version="1.0" encoding="utf-8"?>
88+<root>
89+ <a>content<b>content</b>content</a>
90+</root>
91+|]
92+
93+docDeepMultiple1 :: Document
94+docDeepMultiple1 = parseText_ def [lt|
95+<?xml version="1.0" encoding="utf-8"?>
96+<root>
97+ <a>
98+ <b>content1</b>
99+ <b>content2</b>
100+ </a>
101+ <a>
102+ <b>content3</b>
103+ </a>
104+ <a/>
105+</root>
106+|]
107+
108+docDeepMultiple2 :: Document
109+docDeepMultiple2 = parseText_ def [lt|
110+<?xml version="1.0" encoding="utf-8"?>
111+<root>
112+ <a/>
113+ <a>
114+ <b>content1</b>
115+ </a>
116+ <a>
117+ <b>content2</b>
118+ <b>content3</b>
119+ </a>
120+</root>
121+|]
122+
123+specParser
124+ :: String -- ^ Name of spec
125+ -> Document -- ^ Document to parse
126+ -> (a -> Maybe String) -- ^ Value checker, Nothing if all ok
127+ -> DomParser Identity a -- ^ Parser itself
128+ -> Spec
129+specParser name doc check parser = it name $ do
130+ result <- either throwIO return $ runDomParser doc parser
131+ case check result of
132+ Nothing -> return ()
133+ Just e -> throwIO $ ErrorCall e
134+
135+specParserEq
136+ :: (Eq a, Show a)
137+ => String -- ^ Name of spec
138+ -> Document -- ^ Document to parse
139+ -> a -- ^ Value parser should return
140+ -> DomParser Identity a -- ^ Parser itself
141+ -> Spec
142+specParserEq name doc a parser = specParser name doc check parser
143+ where
144+ check x | x == a = Nothing
145+ | otherwise = Just $ "should be " ++ show a
146+ ++ " but got " ++ show x
147+
148+specParserFailed
149+ :: (Show a)
150+ => String
151+ -> Document
152+ -> DomParser Identity a
153+ -> Spec
154+specParserFailed name doc parser = it name $ example $ do
155+ let result = runDomParser doc parser
156+ case result of
157+ Right a -> fail $ "Expected parser to fail, but returned" ++ (show a)
158+ Left _ -> return ()
159+
160+specParserFailedPath
161+ :: (Show a)
162+ => String -- ^ Name of spec
163+ -> Document
164+ -> [Text] -- ^ Expteced path in error
165+ -> DomParser Identity a
166+ -> Spec
167+specParserFailedPath name doc path parser = it name $ example $ do
168+ let result = runDomParser doc parser
169+ case result of
170+ Right a -> fail $ "Expected parser to fail, but returned" ++ (show a)
171+ Left (ParserErrors errs) -> case errs ^? folded . pePath of
172+ Nothing -> fail $ "Parser failed, but got no errors to analyze path"
173+ Just p | p == path -> return ()
174+ | otherwise -> fail $ "Path is not exptected: " <> show p
175+
176+combinationsSpec :: Spec
177+combinationsSpec = do
178+ describe "inElem" $ do
179+ let parser = inElem "a" textFromDom
180+ describe "succeeds" $ do
181+ specParserEq "docSimple" docSimple "content" parser
182+ specParserEq "docMultiple" docMultiple "content1" parser
183+ describe "fails" $ do
184+ specParserFailed "docNone" docNone parser
185+ specParserFailed "docEmpty" docEmpty parser
186+
187+ describe "inElemAll" $ do
188+ let parser = inElemAll "a" textFromDom
189+ describe "succeeds" $ do
190+ specParserEq "docNone" docNone [] parser
191+ specParserEq "docSimple" docSimple ["content"] parser
192+ specParserEq "docMultiple" docMultiple
193+ ["content1", "content2", "content3"] parser
194+ describe "fails" $ do
195+ specParserFailed "docEmpty" docEmpty parser
196+
197+ describe "inElemMay" $ do
198+ let parser = inElemMay "a" textFromDom
199+ describe "succeeds" $ do
200+ specParserEq "docNone" docNone Nothing parser
201+ specParserEq "docSimple" docSimple (Just "content") parser
202+ specParserEq "docMultiple" docMultiple (Just "content1") parser
203+ describe "fails" $ do
204+ specParserFailed "docEmpty" docEmpty parser
205+
206+ describe "inElemNe" $ do
207+ let parser = inElemNe "a" textFromDom
208+ describe "succeeds" $ do
209+ specParserEq "docSimple" docSimple (pure "content") parser
210+ specParserEq "docMultiple" docMultiple
211+ (NE.fromList ["content1", "content2", "content3"]) parser
212+ describe "fails" $ do
213+ specParserFailed "docNone" docNone parser
214+ specParserFailed "docEmpty" docEmpty parser
215+
216+ describe "diveElem" $ do
217+ describe "single element" $ do
218+ let parser = diveElem "a" $ inElem "b" textFromDom
219+ describe "succeeds" $ do
220+ specParserEq "docDeepMultiple1" docDeepMultiple1 "content1" parser
221+ specParserEq "docDeepMultiple2" docDeepMultiple2 "content1" parser
222+ describe "fails" $ do
223+ specParserFailedPath "docSimple" docSimple ["root", "a", "b"] parser
224+ describe "multiple elements" $ do
225+ let
226+ parser = diveElem "a" $ inElemAll "b" textFromDom
227+ result = ["content1", "content2", "content3"]
228+ describe "succeeds" $ do
229+ specParserEq "docDeepMultiple1" docDeepMultiple1 result parser
230+ specParserEq "docDeepMultiple2" docDeepMultiple2 result parser
231+ specParserEq "docSimple" docSimple [] parser
232+
233+ describe "ignoreEmpty" $ do
234+ describe "mandatory element" $ do
235+ let parser = inElem "a" $ ignoreEmpty textFromDom
236+ describe "succeeds" $ do
237+ specParserEq "docEmpty" docEmpty Nothing parser
238+ specParserEq "docSimple" docSimple (Just "content") parser
239+ specParserEq "docMultiple" docMultiple (Just "content1") parser
240+ describe "fails" $ do
241+ specParserFailed "docNone" docNone parser
242+ describe "optional element" $ do
243+ let parser = fmap join $ inElemMay "a" $ ignoreEmpty textFromDom
244+ describe "succeeds" $ do
245+ specParserEq "docNone" docNone Nothing parser
246+ specParserEq "docEmpty" docEmpty Nothing parser
247+ specParserEq "docSimple" docSimple (Just "content") parser
248+ specParserEq "docMultiple" docMultiple (Just "content1") parser
249+
250+ describe "checkCurrentName" $ do
251+ describe "succeeded" $ do
252+ specParserEq "root element" docSimple () $ do
253+ checkCurrentName "root"
254+ specParserEq "inner element" docSimple () $ do
255+ inElem "a" $ do
256+ checkCurrentName "a"
257+ describe "fails" $ do
258+ specParserFailedPath "root element" docSimple ["toor"] $ do
259+ checkCurrentName "toor"
260+ specParserFailedPath "inner element" docSimple ["root", "b"] $ do
261+ inElem "a" $ checkCurrentName "b"
262+
263+contentSpec :: Spec
264+contentSpec = do
265+ describe "fails if inner element found" $ do
266+ specParserFailed "docDeep" docDeep $ do
267+ inElem "a" $ textFromDom
268+ specParserFailed "docDeepWithContent" docDeepWithContent $ do
269+ inElem "a" $ textFromDom
270+
271+structSpec :: Spec
272+structSpec = do
273+ describe "succeeds" $ do
274+ let
275+ result = TestStructure
276+ { tsName = "name"
277+ , tsInts = [1,2]
278+ , tsBools = pure True }
279+ specParserEq "docStruct" docStruct result fromDom
280+ describe "fails" $ do
281+ specParserFailed "docSimple" docSimple testStructureFromDom
282+
283+main :: IO ()
284+main = hspec $ do
285+ describe "combinators" $ do
286+ combinationsSpec
287+ describe "content parsing" $ do
288+ contentSpec
289+ describe "expected struct" $ do
290+ structSpec