@@ -2,7 +2,7 @@ module Text.XML.DOM.Parser.Combinators
2 ( -- * Generic combinators to traverse descendants
2 ( -- * Generic combinators to traverse descendants
3 traverseElems
3 traverseElems
4 , inFilteredTrav
4 , inFilteredTrav
5− -- * Using 'DomTraversable'
5+ -- * Using 'Buildable'
6 , inElemTrav
6 , inElemTrav
8 , inElemAll
8 , inElemAll
@@ -15,46 +15,30 @@ module Text.XML.DOM.Parser.Combinators
15 , ignoreElem
15 , ignoreElem
16 , ignoreEmpty
16 , ignoreEmpty
17 , ignoreBlank
17 , ignoreBlank
18− -- * Getting current element's properties
21− , getCurrentAttributes
22− , getCurrentAttribute
23− -- * Current element's checks
25− -- * Parsing element's content
29− -- * Parsing attributes
33−import Control.Lens
20+import Control.Lens
34−import Control.Monad.Except
35−import Control.Monad.Reader
21+import Control.Monad.Reader
36−import Data.List.NonEmpty (NonEmpty(..))
22+import Data.Foldable as F
37−import qualified Data.Map.Strict as M
23+import Data.List as L
38−import Data.Monoid
24+import Data.List.NonEmpty (NonEmpty(..))
39−import Data.Text (Text)
40−import qualified Data.Text as T
25+import Data.Text as T
41−import Data.Traversable
26+import Data.Traversable
42−import Data.Typeable
27+import Text.XML
44−import Text.XML
28+import Text.XML.DOM.Parser.Buildable
45−import Text.XML.DOM.Parser.Types
29+import Text.XML.DOM.Parser.Types
46−import Text.XML.Lens
30+import Text.XML.Lens
49−-- | Generic function to traverse arbitrary inner cursors.
33+-- | Generic function to traverse arbitrary inner elements.
50traverseElems
34traverseElems
51 :: (Monad m, Foldable g, Traversable f)
35 :: (Monad m, Foldable g, Traversable f)
52 => ([Element] -> DomParserT g m (f (DomPath, Element)))
36 => ([Element] -> DomParserT g m (f (DomPath, Element)))
53 -- ^ Takes list of current elements and returns container with
37 -- ^ Takes list of current elements and returns container with
54− -- pairs of subpath (relatively to current element) and element
38+ -- pairs of subpath (relatively to current elements) and element
55 -- to run parser in
39 -- to run parser in
56 -> DomParserT Identity m a
40 -> DomParserT Identity m a
57− -- ^ Parser will be runned for each element found in traversable
41+ -- ^ Parser to run for each element found in traversable 'f'
58 -> DomParserT g m (f a)
42 -> DomParserT g m (f a)
59traverseElems trav parser = do
43traverseElems trav parser = do
@@ -64,68 +48,73 @@ traverseElems trav parser = do
64 newpd = ParserData
48 newpd = ParserData
65 { _pdElements = Identity e
49 { _pdElements = Identity e
66 , _pdPath = pd ^. pdPath <> subpath }
50 , _pdPath = pd ^. pdPath <> subpath }
67− -- type of reader has been changed, local won't work here
51+ lift $ runReaderT parser newpd
68− magnify (to $ const newpd) parser
70−-- | Takes function filtering
53+-- | Traverses elements located in same path using filtering function
71inFilteredTrav
54inFilteredTrav
72− :: (Monad m, Foldable g, DomTraversable f)
55+ :: (Monad m, Foldable g, Buildable f)
73 => ([Element] -> (DomPath, [Element]))
56 => ([Element] -> (DomPath, [Element]))
74− -- ^ Function returning some filtered elements with path suffixes which will
57+ -- ^ Takes list of current elements and returns some descendants
75− -- be appended to parser's state
58+ -- subset and path this descendants located at. Path is should be
59+ -- same for all descendants and required for error message
76 -> DomParserT Identity m a
60 -> DomParserT Identity m a
77 -> DomParserT g m (f a)
61 -> DomParserT g m (f a)
78inFilteredTrav deeper = traverseElems trav
62inFilteredTrav deeper = traverseElems trav
80 trav e = do
64 trav e = do
81 let (path, elems) = deeper e
65 let (path, elems) = deeper e
82− case buildDomTraversable elems of
66+ case build elems of
83 Nothing -> throwParserError $ PENotFound . (<> path)
67 Nothing -> throwParserError $ PENotFound . (<> path)
84 Just tr -> return $ fmap (path,) tr
68 Just tr -> return $ fmap (path,) tr
70+-- | Runs parser arbitrary times, depending on 'Buildable' instance of
71+-- 'f'. For example if 'f' becomes 'NonEmpty' then 'inElemTrav' finds
72+-- @one or more@ elements matched by given 'ElemMatcher' and run
73+-- parser in each found element, then returns @NonEmpty a@ of results.
87− :: (Monad m, Foldable g, DomTraversable f)
75+ :: (Monad m, Foldable g, Buildable f)
88− => Text
76+ => ElemMatcher -- ^ Tag(s) matcher to traverse in
89 -> DomParserT Identity m a
77 -> DomParserT Identity m a
90 -> DomParserT g m (f a)
78 -> DomParserT g m (f a)
91inElemTrav n = inFilteredTrav deeper
79inElemTrav n = inFilteredTrav deeper
93− deeper = ([n],) . toListOf (folded . nodes . folded . _Element . ell n)
81+ elemsFold = folded . nodes . folded . _Element . elMatch n
82+ deeper = (DomPath [_emShow n],) . toListOf elemsFold
95−-- | Runs parser inside first children element with given name
84+-- | Runs parser inside first children element matched by macher
97 :: (Monad m, Foldable g)
86 :: (Monad m, Foldable g)
98− => Text
87+ => ElemMatcher
99 -> DomParserT Identity m a
88 -> DomParserT Identity m a
100 -> DomParserT g m a
89 -> DomParserT g m a
101inElem n = fmap runIdentity . inElemTrav n
90inElem n = fmap runIdentity . inElemTrav n
104 :: (Monad m, Foldable g)
93 :: (Monad m, Foldable g)
105− => Text
94+ => ElemMatcher
106 -> DomParserT Identity m a
95 -> DomParserT Identity m a
107 -> DomParserT g m [a]
96 -> DomParserT g m [a]
108inElemAll = inElemTrav
97inElemAll = inElemTrav
111 :: (Monad m, Foldable g)
100 :: (Monad m, Foldable g)
112− => Text
101+ => ElemMatcher
113 -> DomParserT Identity m a
102 -> DomParserT Identity m a
114 -> DomParserT g m (Maybe a)
103 -> DomParserT g m (Maybe a)
115inElemMay = inElemTrav
104inElemMay = inElemTrav
118 :: (Monad m, Foldable g)
107 :: (Monad m, Foldable g)
119− => Text
108+ => ElemMatcher
120 -> DomParserT Identity m a
109 -> DomParserT Identity m a
121 -> DomParserT g m (NonEmpty a)
110 -> DomParserT g m (NonEmpty a)
122inElemNe = inElemTrav
111inElemNe = inElemTrav
124{- | Dive given parser's current tags set into the given path. The @divePath
113{- | Dive given parser's current tags set into the given path. The @divePath
125−["a", "b"]@ differs from @inElem "a" . inElem "b"@. Namely the first variant
114+["a", "b"]@ differs from @inElem "a" $ inElem "b"@. Namely the first variant
126will not fail if occured tag "a" which does not contains tag "b". This
115will not fail if occured tag "a" which does not contains tag "b". This
127behaviour is desireable when you dont want to parse whole XML and just want
116behaviour is desireable when you dont want to parse whole XML and just want
128−to pull tags in some path. The other difference is in traversing inner
117+to pull tags located in some path. The other difference is in traversing inner
129elements. Consider this code
118elements. Consider this code
@@ -143,26 +132,31 @@ which translates like: @a > b > c > fromDom@
143As you can see, inElem always takes first element and runs inner parser in this
132As you can see, inElem always takes first element and runs inner parser in this
144single element, unlike 'divePath' which runs inner parser @in all@ descendants
133single element, unlike 'divePath' which runs inner parser @in all@ descendants
145in given path.
134in given path.
136+Note also that 'divePath' takes parser parameterized by @[]@ not by
137+'Identity'. This because when you dive using some path you will get a
138+list of found elements and all these elements will be @current@ for
149 :: forall m g a
143 :: forall m g a
150 . (Monad m, Foldable g)
144 . (Monad m, Foldable g)
151− => [Text]
145+ => [ElemMatcher]
152 -> DomParserT [] m a
146 -> DomParserT [] m a
153 -> DomParserT g m a
147 -> DomParserT g m a
154divePath path = magnify $ to modElems
148divePath path = magnify $ to modElems
157 = over pdElements (toListOf $ folded . diver)
151 = over pdElements (toListOf $ folded . diver)
158− . over pdPath (<> path)
152+ . over pdPath (<> DomPath (L.map _emShow path))
159 diver :: Fold Element Element
153 diver :: Fold Element Element
160− diver = foldr (.) id $ map toDive path
154+ diver = F.foldr (.) id $ L.map toDive path
161− toDive n = nodes . folded . _Element . ell n
155+ toDive n = nodes . folded . _Element . elMatch n
164 :: (Monad m, Foldable g)
158 :: (Monad m, Foldable g)
165− => Text
159+ => ElemMatcher
166 -> DomParserT [] m a
160 -> DomParserT [] m a
167 -> DomParserT g m a
161 -> DomParserT g m a
168diveElem p = divePath [p]
162diveElem p = divePath [p]
@@ -189,7 +183,7 @@ ignoreEmpty
189 -> DomParserT Identity m (Maybe a)
183 -> DomParserT Identity m (Maybe a)
190ignoreEmpty = ignoreElem test
184ignoreEmpty = ignoreElem test
192− test e = null $ e ^. nodes
186+ test e = L.null $ e ^. nodes
194-- | If all current elements contains blank content, or contains nothing at all
188-- | If all current elements contains blank content, or contains nothing at all
195-- , then returns Nothing, else runs parser.
189-- , then returns Nothing, else runs parser.
@@ -203,114 +197,6 @@ ignoreBlank = ignoreElem test
204 elems = e ^.. nodes . folded . _Element
198 elems = e ^.. nodes . folded . _Element
205 cont = mconcat $ e ^.. nodes . folded . _Content
199 cont = mconcat $ e ^.. nodes . folded . _Content
206− in if | not $ null elems -> False
200+ in if | not $ L.null elems -> False
207 | T.null $ T.strip cont -> True
201 | T.null $ T.strip cont -> True
208 | otherwise -> False
202 | otherwise -> False
210−-- | Returns name of current element.
213−getCurrentName :: (Monad m) => DomParserT Identity m Text
214−getCurrentName = view $ pdElements . to runIdentity . localName
216−-- | If name of current tag differs from first argument throws 'PENotFound' with
217−-- tag name replaced in last path's segment. Useful for checking root
218−-- document's element name.
222− -> DomParserT Identity m ()
223−checkCurrentName n = do
224− cn <- getCurrentName
225− unless (cn == n) $ do
227− let pinit = if null p then [] else init p
228− throwError $ ParserErrors [PENotFound $ pinit ++ [n]]
231−-- | Get current content. If current element contains no content or
232−-- have inner elements then Nothing returned
235−getCurrentContent :: (Monad m) => DomParserT Identity m (Maybe Text)
236−getCurrentContent = do
237− nds <- view $ pdElements . to runIdentity . nodes
240− els = nds ^.. folded . _Element
242− conts = nds ^.. folded . _Content
244− | not $ null els -> Nothing
245− | null conts -> Nothing
246− | otherwise -> Just $ mconcat conts
248−-- | Parses content inside current tag. It expects current element set
249−-- consists of exactly ONE element. If current element does not
250−-- contains content or have other elements as childs then throws error
253− => (Text -> Either Text a)
254− -- ^ Content parser, return error msg if value is not parsed
255− -> DomParserT Identity m a
256−parseContent parse = getCurrentContent >>= \case
257− Nothing -> throwParserError PEContentNotFound
258− Just c -> case parse c of
259− Left e -> throwParserError $ PEWrongFormat e
262−-- | If reader returns 'Nothing' then resulting function returns 'Left
269− => (Text -> Maybe a)
270− -- ^ Content or attribute reader
272− -- ^ Content or attribute value
274−maybeReadContent f t = maybe (Left msg) Right $ f t
276− msg = "Not readable " <> n <> ": " <> t
277− n = T.pack $ show $ typeRep (Proxy :: Proxy a)
279−-- | Tries to read given text to value using 'Read'. Useful to use
280−-- with 'parseContent' and 'parseAttribute'
282− :: (Read a, Typeable a)
285−readContent = maybeReadContent $ readMaybe . T.unpack . T.strip
287−-- | Retuns map of attributes of current element
290−getCurrentAttributes :: (Monad m) => DomParserT Identity m (M.Map Name Text)
291−getCurrentAttributes = view $ pdElements . to runIdentity . attrs
293−-- | Returns element with given name or 'Nothing'
296−getCurrentAttribute :: (Monad m) => Text -> DomParserT Identity m (Maybe Text)
297−getCurrentAttribute attrName'
298− = preview $ pdElements . to runIdentity . attr attrName
300− attrName = Name attrName' Nothing Nothing
302−-- | Parses attribute with given name, throws error if attribute is not found.
309− -> (Text -> Either Text a)
310− -- ^ Attribute content parser
311− -> DomParserT Identity m a
312−parseAttribute attrName parser = getCurrentAttribute attrName >>= \case
313− Nothing -> throwParserError $ PEAttributeNotFound attrName
314− Just aval -> case parser aval of
315− Left err -> throwParserError $ PEAttributeWrongFormat attrName err