@@ -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
@@ -22,24 +22,23 @@ import Control.Monad.Reader
22import Data.Foldable as F
22import Data.Foldable as F
23import Data.List as L
23import Data.List as L
24import Data.List.NonEmpty (NonEmpty(..))
24import Data.List.NonEmpty (NonEmpty(..))
26import Data.Text as T
25import Data.Text as T
27import Data.Traversable
26import Data.Traversable
28import Text.XML
27import Text.XML
29−import Text.XML.DOM.Parser.Common
28+import Text.XML.DOM.Parser.Buildable
30import Text.XML.DOM.Parser.Types
29import Text.XML.DOM.Parser.Types
31import Text.XML.Lens
30import Text.XML.Lens
34−-- | Generic function to traverse arbitrary inner cursors.
33+-- | Generic function to traverse arbitrary inner elements.
35traverseElems
34traverseElems
36 :: (Monad m, Foldable g, Traversable f)
35 :: (Monad m, Foldable g, Traversable f)
37 => ([Element] -> DomParserT g m (f (DomPath, Element)))
36 => ([Element] -> DomParserT g m (f (DomPath, Element)))
38 -- ^ Takes list of current elements and returns container with
37 -- ^ Takes list of current elements and returns container with
39− -- pairs of subpath (relatively to current element) and element
38+ -- pairs of subpath (relatively to current elements) and element
40 -- to run parser in
39 -- to run parser in
41 -> DomParserT Identity m a
40 -> DomParserT Identity m a
42− -- ^ Parser will be runned for each element found in traversable
41+ -- ^ Parser to run for each element found in traversable 'f'
43 -> DomParserT g m (f a)
42 -> DomParserT g m (f a)
44traverseElems trav parser = do
43traverseElems trav parser = do
@@ -49,15 +48,15 @@ traverseElems trav parser = do
49 newpd = ParserData
48 newpd = ParserData
50 { _pdElements = Identity e
49 { _pdElements = Identity e
51 , _pdPath = pd ^. pdPath <> subpath }
50 , _pdPath = pd ^. pdPath <> subpath }
52− -- type of reader has been changed, local won't work here
51+ lift $ runReaderT parser newpd
53− magnify (to $ const newpd) parser
55−-- | Takes function filtering
53+-- | Traverses elements located in same path using filtering function
56inFilteredTrav
54inFilteredTrav
57 :: (Monad m, Foldable g, Buildable f)
55 :: (Monad m, Foldable g, Buildable f)
58 => ([Element] -> (DomPath, [Element]))
56 => ([Element] -> (DomPath, [Element]))
59− -- ^ Function returning some filtered elements with path suffixes which will
57+ -- ^ Takes list of current elements and returns some descendants
60− -- 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
61 -> DomParserT Identity m a
60 -> DomParserT Identity m a
62 -> DomParserT g m (f a)
61 -> DomParserT g m (f a)
63inFilteredTrav deeper = traverseElems trav
62inFilteredTrav deeper = traverseElems trav
@@ -68,49 +67,54 @@ inFilteredTrav deeper = traverseElems trav
68 Nothing -> throwParserError $ PENotFound . (<> path)
67 Nothing -> throwParserError $ PENotFound . (<> path)
69 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.
72 :: (Monad m, Foldable g, Buildable f)
75 :: (Monad m, Foldable g, Buildable f)
73− => NameMatcher -- ^ Name of tag to traverse in
76+ => ElemMatcher -- ^ Tag(s) matcher to traverse in
74 -> DomParserT Identity m a
77 -> DomParserT Identity m a
75 -> DomParserT g m (f a)
78 -> DomParserT g m (f a)
76inElemTrav n = inFilteredTrav deeper
79inElemTrav n = inFilteredTrav deeper
81+ elemsFold = folded . nodes . folded . _Element . elMatch n
78− deeper = (DomPath [_nmShow n],) . toListOf (folded . nodes . folded . _Element . elMatchName n)
82+ deeper = (DomPath [_emShow n],) . toListOf elemsFold
80−-- | Runs parser inside first children element with given name
84+-- | Runs parser inside first children element matched by macher
82 :: (Monad m, Foldable g)
86 :: (Monad m, Foldable g)
83− => NameMatcher
87+ => ElemMatcher
84 -> DomParserT Identity m a
88 -> DomParserT Identity m a
85 -> DomParserT g m a
89 -> DomParserT g m a
86inElem n = fmap runIdentity . inElemTrav n
90inElem n = fmap runIdentity . inElemTrav n
89 :: (Monad m, Foldable g)
93 :: (Monad m, Foldable g)
90− => NameMatcher
94+ => ElemMatcher
91 -> DomParserT Identity m a
95 -> DomParserT Identity m a
92 -> DomParserT g m [a]
96 -> DomParserT g m [a]
93inElemAll = inElemTrav
97inElemAll = inElemTrav
96 :: (Monad m, Foldable g)
100 :: (Monad m, Foldable g)
97− => NameMatcher
101+ => ElemMatcher
98 -> DomParserT Identity m a
102 -> DomParserT Identity m a
99 -> DomParserT g m (Maybe a)
103 -> DomParserT g m (Maybe a)
100inElemMay = inElemTrav
104inElemMay = inElemTrav
103 :: (Monad m, Foldable g)
107 :: (Monad m, Foldable g)
104− => NameMatcher
108+ => ElemMatcher
105 -> DomParserT Identity m a
109 -> DomParserT Identity m a
106 -> DomParserT g m (NonEmpty a)
110 -> DomParserT g m (NonEmpty a)
107inElemNe = inElemTrav
111inElemNe = inElemTrav
109{- | 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
110−["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
111will 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
112behaviour 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
113−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
114elements. Consider this code
118elements. Consider this code
@@ -128,26 +132,31 @@ which translates like: @a > b > c > fromDom@
128As 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
129single element, unlike 'divePath' which runs inner parser @in all@ descendants
133single element, unlike 'divePath' which runs inner parser @in all@ descendants
130in 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
134 :: forall m g a
143 :: forall m g a
135 . (Monad m, Foldable g)
144 . (Monad m, Foldable g)
136− => [NameMatcher]
145+ => [ElemMatcher]
137 -> DomParserT [] m a
146 -> DomParserT [] m a
138 -> DomParserT g m a
147 -> DomParserT g m a
139divePath path = magnify $ to modElems
148divePath path = magnify $ to modElems
142 = over pdElements (toListOf $ folded . diver)
151 = over pdElements (toListOf $ folded . diver)
143− . over pdPath (<> DomPath (L.map _nmShow path))
152+ . over pdPath (<> DomPath (L.map _emShow path))
144 diver :: Fold Element Element
153 diver :: Fold Element Element
145 diver = F.foldr (.) id $ L.map toDive path
154 diver = F.foldr (.) id $ L.map toDive path
146− toDive n = nodes . folded . _Element . elMatchName n
155+ toDive n = nodes . folded . _Element . elMatch n
149 :: (Monad m, Foldable g)
158 :: (Monad m, Foldable g)
150− => NameMatcher
159+ => ElemMatcher
151 -> DomParserT [] m a
160 -> DomParserT [] m a
152 -> DomParserT g m a
161 -> DomParserT g m a
153diveElem p = divePath [p]
162diveElem p = divePath [p]
@@ -188,6 +197,6 @@ ignoreBlank = ignoreElem test
189 elems = e ^.. nodes . folded . _Element
198 elems = e ^.. nodes . folded . _Element
190 cont = mconcat $ e ^.. nodes . folded . _Content
199 cont = mconcat $ e ^.. nodes . folded . _Content
191− in if | not $ L.null elems -> False
200+ in if | not $ L.null elems -> False
192 | T.null $ T.strip cont -> True
201 | T.null $ T.strip cont -> True
193 | otherwise -> False
202 | otherwise -> False