git.iko.soy/typeable/dom-parser
on master help sign in
Upd: add support for parsing attributes
f7ff370Aleksey Uimanov
dom-parser.cabal+2−2
@@ -1,5 +1,5 @@
1name: dom-parser
1name: dom-parser
2−version: 0.1.0
2+version: 1.0.0
3synopsis: Simple monadic DOM parser
3synopsis: Simple monadic DOM parser
4license: MIT
4license: MIT
5license-file: LICENSE
5license-file: LICENSE
@@ -36,11 +36,11 @@ library
36 , TypeOperators
36 , TypeOperators
37 , UndecidableInstances
37 , UndecidableInstances
38 build-depends: base >= 4.7 && < 5
38 build-depends: base >= 4.7 && < 5
39+ , containers
39 , lens
40 , lens
40 , mtl
41 , mtl
41 , open-union
42 , open-union
42 , semigroups
43 , semigroups
43− , shakespeare
44 , text
44 , text
45 , transformers
45 , transformers
46 , type-fun
46 , type-fun
src/Text/XML/DOM/Parser/Class.hs+18−12
@@ -8,11 +8,13 @@ module Text.XML.DOM.Parser.Class
8 , textFromDom
8 , textFromDom
9 , stringFromDom
9 , stringFromDom
10 , charFromDom
10 , charFromDom
11+ , readChar
11 , intFromDom
12 , intFromDom
12 , integerFromDom
13 , integerFromDom
13 , doubleFromDom
14 , doubleFromDom
14 , fixedFromDom
15 , fixedFromDom
15 , boolFromDom
16 , boolFromDom
17+ , readBool
16 , unitFromDom
18 , unitFromDom
17 , voidFromDom
19 , voidFromDom
18 ) where
20 ) where
@@ -20,12 +22,12 @@ module Text.XML.DOM.Parser.Class
20import Control.Applicative
22import Control.Applicative
21import Control.Lens
23import Control.Lens
22import Data.Fixed
24import Data.Fixed
25+import Data.Monoid
23import Data.OpenUnion
26import Data.OpenUnion
24import Data.Text (Text)
27import Data.Text (Text)
25import qualified Data.Text as T
28import qualified Data.Text as T
26import Data.Typeable
29import Data.Typeable
27import Data.Void
30import Data.Void
28−import Text.Shakespeare.Text (st)
29import Text.XML
31import Text.XML
30import Text.XML.DOM.Parser.Combinators
32import Text.XML.DOM.Parser.Combinators
31import Text.XML.DOM.Parser.Types
33import Text.XML.DOM.Parser.Types
@@ -95,16 +97,18 @@ unionFromDom
95unionFromDom _ = fromDom
97unionFromDom _ = fromDom
96
98
97textFromDom :: (Monad m) => DomParserT Identity m Text
99textFromDom :: (Monad m) => DomParserT Identity m Text
98−textFromDom = parseContent pure
100+textFromDom = parseContent Right
99
101
100stringFromDom :: (Monad m) => DomParserT Identity m String
102stringFromDom :: (Monad m) => DomParserT Identity m String
101−stringFromDom = parseContent $ pure . T.unpack
103+stringFromDom = parseContent $ Right . T.unpack
102
104
103charFromDom :: (Monad m) => DomParserT Identity m Char
105charFromDom :: (Monad m) => DomParserT Identity m Char
104−charFromDom = parseContent $ \t -> case T.unpack $ T.strip t of
106+charFromDom = parseContent readChar
105− [c] -> pure c
107+
106− _ -> throwParserError $ PEWrongFormat
108+readChar :: Text -> Either Text Char
109+readChar t = case T.unpack $ T.strip t of
110+ [c] -> Right c
107− "Should have exactly one non-blank character"
111+ _ -> Left "Should have exactly one non-blank character"
108
112
109intFromDom :: (Monad m) => DomParserT Identity m Int
113intFromDom :: (Monad m) => DomParserT Identity m Int
110intFromDom = parseContent readContent
114intFromDom = parseContent readContent
@@ -124,16 +128,18 @@ fixedFromDom = parseContent readContent
124-- f, false or 0 for False value. Case is not significant, blank
128-- f, false or 0 for False value. Case is not significant, blank
125-- characters are striped.
129-- characters are striped.
126boolFromDom :: (Monad m) => DomParserT Identity m Bool
130boolFromDom :: (Monad m) => DomParserT Identity m Bool
127−boolFromDom = parseContent $ \t ->
131+boolFromDom = parseContent readBool
132+
133+readBool :: Text -> Either Text Bool
134+readBool t =
128 let
135 let
129 lowt = T.toLower $ T.strip t
136 lowt = T.toLower $ T.strip t
130 tvals = ["y", "yes", "t", "true", "1"]
137 tvals = ["y", "yes", "t", "true", "1"]
131 fvals = ["n", "no", "f", "false", "0"]
138 fvals = ["n", "no", "f", "false", "0"]
132− in if | lowt `elem` tvals -> return True
139+ in if | lowt `elem` tvals -> Right True
133− | lowt `elem` fvals -> return False
140+ | lowt `elem` fvals -> Right False
134 | otherwise ->
141 | otherwise ->
135− let msg = [st|Could not read "#{t}" as Bool|]
142+ Left $ "Could not read " <> t <> " as Bool"
136− in throwParserError $ PEWrongFormat msg
137
143
138-- | Always successfully parses any DOM to @()@
144-- | Always successfully parses any DOM to @()@
139unitFromDom :: (Monad m) => DomParserT Identity m ()
145unitFromDom :: (Monad m) => DomParserT Identity m ()
src/Text/XML/DOM/Parser/Combinators.hs+89−29
@@ -15,24 +15,32 @@ module Text.XML.DOM.Parser.Combinators
15 , ignoreElem
15 , ignoreElem
16 , ignoreEmpty
16 , ignoreEmpty
17 , ignoreBlank
17 , ignoreBlank
18− -- * Checking current element properties
18+ -- * Getting current element's properties
19+ , getCurrentName
20+ , getCurrentContent
21+ , getCurrentAttributes
22+ , getCurrentAttribute
23+ -- * Current element's checks
19 , checkCurrentName
24 , checkCurrentName
20− -- * Parsing arbitrary content
25+ -- * Parsing element's content
21 , parseContent
26 , parseContent
22 , readContent
27 , readContent
28+ , maybeReadContent
29+ -- * Parsing attributes
30+ , parseAttribute
23 ) where
31 ) where
24
32
25import Control.Lens
33import Control.Lens
26import Control.Monad.Except
34import Control.Monad.Except
27import Control.Monad.Reader
35import Control.Monad.Reader
28import Data.List.NonEmpty (NonEmpty(..))
36import Data.List.NonEmpty (NonEmpty(..))
37+import qualified Data.Map.Strict as M
29import Data.Monoid
38import Data.Monoid
30import Data.Text (Text)
39import Data.Text (Text)
31import qualified Data.Text as T
40import qualified Data.Text as T
32import Data.Traversable
41import Data.Traversable
33import Data.Typeable
42import Data.Typeable
34import Text.Read
43import Text.Read
35−import Text.Shakespeare.Text (st)
36import Text.XML
44import Text.XML
37import Text.XML.DOM.Parser.Types
45import Text.XML.DOM.Parser.Types
38import Text.XML.Lens
46import Text.XML.Lens
@@ -41,8 +49,10 @@ import Text.XML.Lens
41-- | Generic function to traverse arbitrary inner cursors.
49-- | Generic function to traverse arbitrary inner cursors.
42traverseElems
50traverseElems
43 :: (Monad m, Foldable g, Traversable f)
51 :: (Monad m, Foldable g, Traversable f)
44− => ([Element] -> DomParserT g m (f ([Text], Element)))
52+ => ([Element] -> DomParserT g m (f (DomPath, Element)))
45− -- ^ Takes set of current elements and
53+ -- ^ Takes list of current elements and returns container with
54+ -- pairs of subpath (relatively to current element) and element
55+ -- to run parser in
46 -> DomParserT Identity m a
56 -> DomParserT Identity m a
47 -- ^ Parser will be runned for each element found in traversable
57 -- ^ Parser will be runned for each element found in traversable
48 -> DomParserT g m (f a)
58 -> DomParserT g m (f a)
@@ -53,12 +63,13 @@ traverseElems trav parser = do
53 let newpd = ParserData
63 let newpd = ParserData
54 { _pdElements = Identity e
64 { _pdElements = Identity e
55 , _pdPath = pd ^. pdPath <> subpath }
65 , _pdPath = pd ^. pdPath <> subpath }
56− magnify (to $ const newpd) parser
66+ magnify (to $ const newpd) parser -- type of reader is changed, so
67+ -- local does not work
57
68
58-- | Takes function filtering
69-- | Takes function filtering
59inFilteredTrav
70inFilteredTrav
60 :: (Monad m, Foldable g, DomTraversable f)
71 :: (Monad m, Foldable g, DomTraversable f)
61− => ([Element] -> ([Text], [Element]))
72+ => ([Element] -> (DomPath, [Element]))
62 -- ^ Function returning some filtered elements with path suffixes which will
73 -- ^ Function returning some filtered elements with path suffixes which will
63 -- be appended to parser's state
74 -- be appended to parser's state
64 -> DomParserT Identity m a
75 -> DomParserT Identity m a
@@ -160,7 +171,7 @@ ignoreElem
160 :: (Monad m)
171 :: (Monad m)
161 => (Element -> Bool)
172 => (Element -> Bool)
162 -- ^ Predicate checking that we must ignore some current tag. If returns
173 -- ^ Predicate checking that we must ignore some current tag. If returns
163− -- true then parser will not be runned and combinator just returns Nothing.
174+ -- 'True' then parser will not be runned and combinator just returns Nothing.
164 -> DomParserT Identity m a
175 -> DomParserT Identity m a
165 -> DomParserT Identity m (Maybe a)
176 -> DomParserT Identity m (Maybe a)
166ignoreElem test parser = do
177ignoreElem test parser = do
@@ -195,6 +206,10 @@ ignoreBlank = ignoreElem test
195 | T.null $ T.strip cont -> True
206 | T.null $ T.strip cont -> True
196 | otherwise -> False
207 | otherwise -> False
197
208
209+-- | Returns name of current element.
210+getCurrentName :: (Monad m) => DomParserT Identity m Text
211+getCurrentName = view $ pdElements . to runIdentity . localName
212+
198-- | If name of current tag differs from first argument throws 'PENotFound' with
213-- | If name of current tag differs from first argument throws 'PENotFound' with
199-- tag name replaced in last path's segment. Usefull for checking root
214-- tag name replaced in last path's segment. Usefull for checking root
200-- document's element name.
215-- document's element name.
@@ -203,37 +218,82 @@ checkCurrentName
203 => Text
218 => Text
204 -> DomParserT Identity m ()
219 -> DomParserT Identity m ()
205checkCurrentName n = do
220checkCurrentName n = do
206− cn <- view $ pdElements . to runIdentity . localName
221+ cn <- getCurrentName
207 unless (cn == n) $ do
222 unless (cn == n) $ do
208 p <- view pdPath
223 p <- view pdPath
209 let pinit = if null p then [] else init p
224 let pinit = if null p then [] else init p
210 throwError $ ParserErrors [PENotFound $ pinit ++ [n]]
225 throwError $ ParserErrors [PENotFound $ pinit ++ [n]]
211 return ()
226 return ()
212
227
213−-- | Parses content inside current tag. It expects current element set consists
228+-- | Get current content. If current element contains no content or
214−-- of exactly ONE element. Throws error if current elements set contains
229+-- have inner elements then Nothing returned
215−-- multiple of them.
230+getCurrentContent :: (Monad m) => DomParserT Identity m (Maybe Text)
216−parseContent
231+getCurrentContent = do
217− :: (Monad m)
218− => (Text -> DomParserT Identity m a)
219− -> DomParserT Identity m a
220−parseContent parse = do
221− e <- view $ pdElements . to runIdentity
232+ nds <- view $ pdElements . to runIdentity . nodes
222 let
233 let
223− nds = e ^. nodes
234+ els :: [Element]
224 els = nds ^.. folded . _Element
235 els = nds ^.. folded . _Element
236+ conts :: [Text]
225 conts = nds ^.. folded . _Content
237 conts = nds ^.. folded . _Content
226− when (not $ null els) $ throwParserError PEContentNotFound
238+ return $ if
227− when (null conts) $ throwParserError PEContentNotFound
239+ | not $ null els -> Nothing
240+ | null conts -> Nothing
228− parse $ mconcat conts
241+ | otherwise -> Just $ mconcat conts
242+
243+-- | Parses content inside current tag. It expects current element set
244+-- consists of exactly ONE element. If current element does not
245+-- contains content or have other elements as childs then throws error
246+parseContent
247+ :: (Monad m)
248+ => (Text -> Either Text a)
249+ -- ^ Content parser, return error msg if value is not parsed
250+ -> DomParserT Identity m a
251+parseContent parse = getCurrentContent >>= \case
252+ Nothing -> throwParserError PEContentNotFound
253+ Just c -> case parse c of
254+ Left e -> throwParserError $ PEWrongFormat e
255+ Right a -> return a
256+
257+maybeReadContent
258+ :: forall a
259+ . (Typeable a)
260+ => (Text -> Maybe a)
261+ -- ^ Content or attribute reader
262+ -> Text
263+ -- ^ Content or attribute value
264+ -> Either Text a
265+maybeReadContent f t = maybe (Left msg) Right $ f t
266+ where
267+ msg = "Not readable " <> n <> ": " <> t
268+ n = T.pack $ show $ typeRep (Proxy :: Proxy a)
229
269
270+-- | Tries to read given text to value using 'Read'. Usefull to use
271+-- with 'parseContent' and 'parseAttribute'
230readContent
272readContent
231− :: forall m g a
232− . (Read a, Typeable a, Monad m)
273+ :: (Read a, Typeable a)
233 => Text
274 => Text
234− -> DomParserT g m a
275+ -> Either Text a
235−readContent t = case readMaybe $ T.unpack t of
276+readContent = maybeReadContent $ readMaybe . T.unpack . T.strip
236− Nothing -> throwParserError $ PEWrongFormat [st|Not readable #{n}: #{t}|]
277+
237− Just a -> pure a
278+getCurrentAttributes :: (Monad m) => DomParserT Identity m (M.Map Name Text)
279+getCurrentAttributes = view $ pdElements . to runIdentity . attrs
280+
281+getCurrentAttribute :: (Monad m) => Text -> DomParserT Identity m (Maybe Text)
282+getCurrentAttribute attrName'
283+ = preview $ pdElements . to runIdentity . attr attrName
238 where
284 where
239− n = show $ typeRep (Proxy :: Proxy a)
285+ attrName = Name attrName' Nothing Nothing
286+
287+-- | Parses attribute with given name, throws error if attribute is not found.
288+parseAttribute
289+ :: (Monad m)
290+ => Text
291+ -- ^ Attribute name
292+ -> (Text -> Either Text a)
293+ -- ^ Attribute content parser
294+ -> DomParserT Identity m a
295+parseAttribute attrName parser = getCurrentAttribute attrName >>= \case
296+ Nothing -> throwParserError $ PEAttributeNotFound attrName
297+ Just aval -> case parser aval of
298+ Left err -> throwParserError $ PEAttributeWrongFormat attrName err
299+ Right a -> return a
src/Text/XML/DOM/Parser/Types.hs+23−6
@@ -1,8 +1,10 @@
1module Text.XML.DOM.Parser.Types
1module Text.XML.DOM.Parser.Types
2 ( -- * Parser internals
2 ( -- * Parser internals
3+ DomPath
3− ParserError(..)
4+ , ParserError(..)
4 , pePath
5 , pePath
5 , peDetails
6 , peDetails
7+ , peAttributeName
6 , ParserErrors(..)
8 , ParserErrors(..)
7 , _ParserErrors
9 , _ParserErrors
8 , ParserData(..)
10 , ParserData(..)
@@ -31,28 +33,43 @@ import GHC.Generics (Generic)
31import Text.XML
33import Text.XML
32import Text.XML.Lens
34import Text.XML.Lens
33
35
36+type DomPath = [Text]
37+
34-- | DOM parser error description.
38-- | DOM parser error description.
35data ParserError
39data ParserError
36 -- | Tag not found which should be.
40 -- | Tag not found which should be.
37 = PENotFound
41 = PENotFound
38− { _pePath :: [Text]
42+ { _pePath :: DomPath
39 }
43 }
40
44
41 -- | Tag contents has wrong format, (could not read text to value)
45 -- | Tag contents has wrong format, (could not read text to value)
42 | PEWrongFormat
46 | PEWrongFormat
43 { _peDetails :: Text
47 { _peDetails :: Text
44− , _pePath :: [Text] -- ^ path of element
48+ , _pePath :: DomPath -- ^ path of element
49+ }
50+
51+ -- | Could not parse attribute
52+ | PEAttributeWrongFormat
53+ { _peAttributeName :: Text
54+ , _peDetails :: Text
55+ , _pePath :: DomPath
45 }
56 }
46
57
47 -- | Node should have text content, but it does not.
58 -- | Node should have text content, but it does not.
48 | PEContentNotFound
59 | PEContentNotFound
49− { _pePath :: [Text]
60+ { _pePath :: DomPath
61+ }
62+
63+ -- | Expected attribute but not found
64+ | PEAttributeNotFound
65+ { _peAttributeName :: Text
66+ , _pePath :: DomPath
50 }
67 }
51
68
52 -- | Some other error
69 -- | Some other error
53 | PEOther
70 | PEOther
54 { _peDetails :: Text
71 { _peDetails :: Text
55− , _pePath :: [Text]
72+ , _pePath :: DomPath
56 } deriving (Eq, Ord, Show, Generic)
73 } deriving (Eq, Ord, Show, Generic)
57
74
58makeLenses ''ParserError
75makeLenses ''ParserError
@@ -86,7 +103,7 @@ data ParserData f = ParserData
86 { _pdElements :: f Element
103 { _pdElements :: f Element
87 -- ^ Current element(s). Functor is intended to be either @Identity@ or
104 -- ^ Current element(s). Functor is intended to be either @Identity@ or
88 -- @[]@
105 -- @[]@
89− , _pdPath :: [Text]
106+ , _pdPath :: DomPath
90 -- ^ Path for error reporting
107 -- ^ Path for error reporting
91 }
108 }
92
109
test/Main.hs+88−21
@@ -27,8 +27,15 @@ testStructureFromDom = do
27 tsBools <- inElemNe "bool" boolFromDom
27 tsBools <- inElemNe "bool" boolFromDom
28 return TestStructure{..}
28 return TestStructure{..}
29
29
30+testStructureAttributes :: (Monad m) => DomParserT Identity m TestStructure
31+testStructureAttributes = do
32+ name <- getCurrentName
33+ int <- parseAttribute "int" readContent
34+ bool <- parseAttribute "bool" readBool
30−instance FromDom TestStructure where
35+ return $ TestStructure
31− fromDom = testStructureFromDom
36+ { tsName = name
37+ , tsInts = [int]
38+ , tsBools = pure bool }
32
39
33docStruct :: Document
40docStruct :: Document
34docStruct = parseText_ def [lt|
41docStruct = parseText_ def [lt|
@@ -41,6 +48,13 @@ docStruct = parseText_ def [lt|
41</root>
48</root>
42|]
49|]
43
50
51+docAttrStruct :: Document
52+docAttrStruct = parseText_ def [lt|
53+<?xml version="1.0" encoding="utf-8"?>
54+<structure int="10" bool="t">
55+</structure>
56+|]
57+
44docNone :: Document
58docNone :: Document
45docNone = parseText_ def [lt|
59docNone = parseText_ def [lt|
46<?xml version="1.0" encoding="utf-8"?>
60<?xml version="1.0" encoding="utf-8"?>
@@ -48,8 +62,26 @@ docNone = parseText_ def [lt|
48</root>
62</root>
49|]
63|]
50
64
65+docSimpleAttr :: Document
66+docSimpleAttr = parseText_ def [lt|
67+<?xml version="1.0" encoding="utf-8"?>
68+<root>
69+<a attr="content"/>
70+</root>
71+|]
72+
73+docMultipleAttr :: Document
74+docMultipleAttr = parseText_ def [lt|
75+<?xml version="1.0" encoding="utf-8"?>
76+<root>
77+<a attr="content1"/>
78+<a attr="content2"/>
79+<a attr="content3"/>
80+</root>
81+|]
82+
51−docEmpty :: Document
83+docSingleEmpty :: Document
52−docEmpty = parseText_ def [lt|
84+docSingleEmpty = parseText_ def [lt|
53<?xml version="1.0" encoding="utf-8"?>
85<?xml version="1.0" encoding="utf-8"?>
54<root>
86<root>
55 <a/>
87 <a/>
@@ -176,13 +208,37 @@ specParserFailedPath name doc path parser = it name $ example $ do
176combinationsSpec :: Spec
208combinationsSpec :: Spec
177combinationsSpec = do
209combinationsSpec = do
178 describe "inElem" $ do
210 describe "inElem" $ do
179− let parser = inElem "a" textFromDom
180 describe "succeeds" $ do
211 describe "succeeds" $ do
212+ describe "simple" $ do
213+ let parser = inElem "a" textFromDom
181− specParserEq "docSimple" docSimple "content" parser
214+ specParserEq "docSimple" docSimple "content" parser
182− specParserEq "docMultiple" docMultiple "content1" parser
215+ specParserEq "docMultiple" docMultiple "content1" parser
216+ describe "deep" $ do
217+ let parser = inElem "a" $ inElem "b" textFromDom
218+ specParserEq "docDeep" docDeep "content" parser
219+ specParserEq "docDeepMultiple1" docDeepMultiple1 "content1" parser
220+ specParserEq "docDeepWithContent" docDeepWithContent "content" parser
183 describe "fails" $ do
221 describe "fails" $ do
222+ describe "simple" $ do
223+ let parser = inElem "a" textFromDom
184− specParserFailed "docNone" docNone parser
224+ specParserFailed "docNone" docNone parser
185− specParserFailed "docEmpty" docEmpty parser
225+ specParserFailed "docSingleEmpty" docSingleEmpty parser
226+ describe "deep" $ do
227+ let parser = inElem "a" $ inElem "b" textFromDom
228+ specParserFailedPath "docDeepMultiple2" docDeepMultiple2
229+ ["root", "a", "b"] parser
230+ -- should fail here because first tag "a" does not contains
231+ -- tag "b" which is expected by parser @inElem "b"@
232+
233+ describe "parseAttribute" $ do
234+ describe "succeeds" $ do
235+ specParserEq "docSimpleAttr" docSimpleAttr "content"
236+ $ inElem "a" $ parseAttribute "attr" Right
237+ let res = ["content1", "content2", "content3"]
238+ specParserEq "docMultipleAttr" docMultipleAttr res
239+ $ inElemAll "a" $ parseAttribute "attr" Right
240+ describe "fails" $ do
241+ specParserFailed "docNone" docNone $ parseAttribute "attr" Right
186
242
187 describe "inElemAll" $ do
243 describe "inElemAll" $ do
188 let parser = inElemAll "a" textFromDom
244 let parser = inElemAll "a" textFromDom
@@ -192,7 +248,7 @@ combinationsSpec = do
192 specParserEq "docMultiple" docMultiple
248 specParserEq "docMultiple" docMultiple
193 ["content1", "content2", "content3"] parser
249 ["content1", "content2", "content3"] parser
194 describe "fails" $ do
250 describe "fails" $ do
195− specParserFailed "docEmpty" docEmpty parser
251+ specParserFailed "docSingleEmpty" docSingleEmpty parser
196
252
197 describe "inElemMay" $ do
253 describe "inElemMay" $ do
198 let parser = inElemMay "a" textFromDom
254 let parser = inElemMay "a" textFromDom
@@ -201,7 +257,7 @@ combinationsSpec = do
201 specParserEq "docSimple" docSimple (Just "content") parser
257 specParserEq "docSimple" docSimple (Just "content") parser
202 specParserEq "docMultiple" docMultiple (Just "content1") parser
258 specParserEq "docMultiple" docMultiple (Just "content1") parser
203 describe "fails" $ do
259 describe "fails" $ do
204− specParserFailed "docEmpty" docEmpty parser
260+ specParserFailed "docSingleEmpty" docSingleEmpty parser
205
261
206 describe "inElemNe" $ do
262 describe "inElemNe" $ do
207 let parser = inElemNe "a" textFromDom
263 let parser = inElemNe "a" textFromDom
@@ -211,7 +267,7 @@ combinationsSpec = do
211 (NE.fromList ["content1", "content2", "content3"]) parser
267 (NE.fromList ["content1", "content2", "content3"]) parser
212 describe "fails" $ do
268 describe "fails" $ do
213 specParserFailed "docNone" docNone parser
269 specParserFailed "docNone" docNone parser
214− specParserFailed "docEmpty" docEmpty parser
270+ specParserFailed "docSingleEmpty" docSingleEmpty parser
215
271
216 describe "diveElem" $ do
272 describe "diveElem" $ do
217 describe "single element" $ do
273 describe "single element" $ do
@@ -234,7 +290,7 @@ combinationsSpec = do
234 describe "mandatory element" $ do
290 describe "mandatory element" $ do
235 let parser = inElem "a" $ ignoreEmpty textFromDom
291 let parser = inElem "a" $ ignoreEmpty textFromDom
236 describe "succeeds" $ do
292 describe "succeeds" $ do
237− specParserEq "docEmpty" docEmpty Nothing parser
293+ specParserEq "docSingleEmpty" docSingleEmpty Nothing parser
238 specParserEq "docSimple" docSimple (Just "content") parser
294 specParserEq "docSimple" docSimple (Just "content") parser
239 specParserEq "docMultiple" docMultiple (Just "content1") parser
295 specParserEq "docMultiple" docMultiple (Just "content1") parser
240 describe "fails" $ do
296 describe "fails" $ do
@@ -243,7 +299,7 @@ combinationsSpec = do
243 let parser = fmap join $ inElemMay "a" $ ignoreEmpty textFromDom
299 let parser = fmap join $ inElemMay "a" $ ignoreEmpty textFromDom
244 describe "succeeds" $ do
300 describe "succeeds" $ do
245 specParserEq "docNone" docNone Nothing parser
301 specParserEq "docNone" docNone Nothing parser
246− specParserEq "docEmpty" docEmpty Nothing parser
302+ specParserEq "docSingleEmpty" docSingleEmpty Nothing parser
247 specParserEq "docSimple" docSimple (Just "content") parser
303 specParserEq "docSimple" docSimple (Just "content") parser
248 specParserEq "docMultiple" docMultiple (Just "content1") parser
304 specParserEq "docMultiple" docMultiple (Just "content1") parser
249
305
@@ -271,14 +327,25 @@ contentSpec = do
271structSpec :: Spec
327structSpec :: Spec
272structSpec = do
328structSpec = do
273 describe "succeeds" $ do
329 describe "succeeds" $ do
274− let
330+ describe "dom structure" $ do
331+ let
275− result = TestStructure
332+ result = TestStructure
276− { tsName = "name"
333+ { tsName = "name"
277− , tsInts = [1,2]
334+ , tsInts = [1,2]
278− , tsBools = pure True }
335+ , tsBools = pure True }
279− specParserEq "docStruct" docStruct result fromDom
336+ specParserEq "docStruct" docStruct result testStructureFromDom
337+ describe "attribute structure" $ do
338+ let
339+ result = TestStructure
340+ { tsName = "structure"
341+ , tsInts = [10]
342+ , tsBools = pure True }
343+ specParserEq "docAttrStruct" docAttrStruct result testStructureAttributes
280 describe "fails" $ do
344 describe "fails" $ do
345+ describe "dom structure" $ do
281− specParserFailed "docSimple" docSimple testStructureFromDom
346+ specParserFailed "docSimple" docSimple testStructureFromDom
347+ describe "attr structure" $ do
348+ specParserFailed "docNone" docNone testStructureAttributes
282
349
283main :: IO ()
350main :: IO ()
284main = hspec $ do
351main = hspec $ do