@@ -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,116 +15,107 @@ 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
22+import Data.Foldable as F
36−import Data.List.NonEmpty (NonEmpty(..))
24+import Data.List.NonEmpty (NonEmpty(..))
37−import qualified Data.Map.Strict as M
38−import Data.Monoid
25+import Data.Monoid
39−import Data.Text (Text)
40−import qualified Data.Text as T
26+import Data.Text as T
41−import Data.Traversable
27+import Data.Traversable
42−import Data.Typeable
28+import Text.XML
44−import Text.XML
29+import Text.XML.DOM.Parser.Buildable
45−import Text.XML.DOM.Parser.Types
30+import Text.XML.DOM.Parser.Types
46−import Text.XML.Lens
31+import Text.XML.Lens
49−-- | Generic function to traverse arbitrary inner cursors.
34+-- | Generic function to traverse arbitrary inner elements.
50traverseElems
35traverseElems
51 :: (Monad m, Foldable g, Traversable f)
36 :: (Monad m, Foldable g, Traversable f)
52 => ([Element] -> DomParserT g m (f (DomPath, Element)))
37 => ([Element] -> DomParserT g m (f (DomPath, Element)))
53 -- ^ Takes list of current elements and returns container with
38 -- ^ Takes list of current elements and returns container with
54− -- pairs of subpath (relatively to current element) and element
39+ -- pairs of subpath (relatively to current elements) and element
55 -- to run parser in
40 -- to run parser in
56 -> DomParserT Identity m a
41 -> DomParserT Identity m a
57− -- ^ Parser will be runned for each element found in traversable
42+ -- ^ Parser to run for each element found in traversable 'f'
58 -> DomParserT g m (f a)
43 -> DomParserT g m (f a)
59traverseElems trav parser = do
44traverseElems trav parser = do
61 inner <- trav $ pd ^.. pdElements . folded
46 inner <- trav $ pd ^.. pdElements . folded
62 for inner $ \(subpath, e) -> do
47 for inner $ \(subpath, e) -> do
63− let newpd = ParserData
49+ newpd = ParserData
64− { _pdElements = Identity e
50+ { _pdElements = Identity e
65− , _pdPath = pd ^. pdPath <> subpath }
51+ , _pdPath = pd ^. pdPath <> subpath }
66− magnify (to $ const newpd) parser -- type of reader is changed, so
52+ lift $ runReaderT parser newpd
67− -- local does not work
69−-- | Takes function filtering
54+-- | Traverses elements located in same path using filtering function
70inFilteredTrav
55inFilteredTrav
71− :: (Monad m, Foldable g, DomTraversable f)
56+ :: (Monad m, Foldable g, Buildable f)
72 => ([Element] -> (DomPath, [Element]))
57 => ([Element] -> (DomPath, [Element]))
73− -- ^ Function returning some filtered elements with path suffixes which will
58+ -- ^ Takes list of current elements and returns some descendants
74− -- be appended to parser's state
59+ -- subset and path this descendants located at. Path is should be
60+ -- same for all descendants and required for error message
75 -> DomParserT Identity m a
61 -> DomParserT Identity m a
76 -> DomParserT g m (f a)
62 -> DomParserT g m (f a)
77inFilteredTrav deeper = traverseElems trav
63inFilteredTrav deeper = traverseElems trav
79 trav e = do
65 trav e = do
80 let (path, elems) = deeper e
66 let (path, elems) = deeper e
81− case buildDomTraversable elems of
67+ case build elems of
82 Nothing -> throwParserError $ PENotFound . (<> path)
68 Nothing -> throwParserError $ PENotFound . (<> path)
83 Just tr -> return $ fmap (path,) tr
69 Just tr -> return $ fmap (path,) tr
71+-- | Runs parser arbitrary times, depending on 'Buildable' instance of
72+-- 'f'. For example if 'f' becomes 'NonEmpty' then 'inElemTrav' finds
73+-- @one or more@ elements matched by given 'ElemMatcher' and run
74+-- parser in each found element, then returns @NonEmpty a@ of results.
86− :: (Monad m, Foldable g, DomTraversable f)
76+ :: (Monad m, Foldable g, Buildable f)
87− => Text
77+ => ElemMatcher -- ^ Tag(s) matcher to traverse in
88 -> DomParserT Identity m a
78 -> DomParserT Identity m a
89 -> DomParserT g m (f a)
79 -> DomParserT g m (f a)
90inElemTrav n = inFilteredTrav deeper
80inElemTrav n = inFilteredTrav deeper
92− deeper = ([n],) . toListOf (folded . nodes . folded . _Element . ell n)
82+ elemsFold = folded . nodes . folded . _Element . elMatch n
83+ deeper = (DomPath [_emShow n],) . toListOf elemsFold
94−-- | Runs parser inside first children element with given name
85+-- | Runs parser inside first children element matched by macher
96 :: (Monad m, Foldable g)
87 :: (Monad m, Foldable g)
97− => Text
88+ => ElemMatcher
98 -> DomParserT Identity m a
89 -> DomParserT Identity m a
99 -> DomParserT g m a
90 -> DomParserT g m a
100inElem n = fmap runIdentity . inElemTrav n
91inElem n = fmap runIdentity . inElemTrav n
103 :: (Monad m, Foldable g)
94 :: (Monad m, Foldable g)
104− => Text
95+ => ElemMatcher
105 -> DomParserT Identity m a
96 -> DomParserT Identity m a
106 -> DomParserT g m [a]
97 -> DomParserT g m [a]
107inElemAll = inElemTrav
98inElemAll = inElemTrav
110 :: (Monad m, Foldable g)
101 :: (Monad m, Foldable g)
111− => Text
102+ => ElemMatcher
112 -> DomParserT Identity m a
103 -> DomParserT Identity m a
113 -> DomParserT g m (Maybe a)
104 -> DomParserT g m (Maybe a)
114inElemMay = inElemTrav
105inElemMay = inElemTrav
117 :: (Monad m, Foldable g)
108 :: (Monad m, Foldable g)
118− => Text
109+ => ElemMatcher
119 -> DomParserT Identity m a
110 -> DomParserT Identity m a
120 -> DomParserT g m (NonEmpty a)
111 -> DomParserT g m (NonEmpty a)
121inElemNe = inElemTrav
112inElemNe = inElemTrav
123{- | Dive given parser's current tags set into the given path. The @divePath
114{- | Dive given parser's current tags set into the given path. The @divePath
124−["a", "b"]@ differs from @inElem "a" . inElem "b"@. Namely the first variant
115+["a", "b"]@ differs from @inElem "a" $ inElem "b"@. Namely the first variant
125will not fail if occured tag "a" which does not contains tag "b". This
116will not fail if occured tag "a" which does not contains tag "b". This
126behaviour is desireable when you dont want to parse whole XML and just want
117behaviour is desireable when you dont want to parse whole XML and just want
127−to pull tags in some path. The other difference is in traversing inner
118+to pull tags located in some path. The other difference is in traversing inner
128elements. Consider this code
119elements. Consider this code
@@ -142,26 +133,31 @@ which translates like: @a > b > c > fromDom@
142As you can see, inElem always takes first element and runs inner parser in this
133As you can see, inElem always takes first element and runs inner parser in this
143single element, unlike 'divePath' which runs inner parser @in all@ descendants
134single element, unlike 'divePath' which runs inner parser @in all@ descendants
144in given path.
135in given path.
137+Note also that 'divePath' takes parser parameterized by @[]@ not by
138+'Identity'. This because when you dive using some path you will get a
139+list of found elements and all these elements will be @current@ for
148 :: forall m g a
144 :: forall m g a
149 . (Monad m, Foldable g)
145 . (Monad m, Foldable g)
150− => [Text]
146+ => [ElemMatcher]
151 -> DomParserT [] m a
147 -> DomParserT [] m a
152 -> DomParserT g m a
148 -> DomParserT g m a
153divePath path = magnify $ to modElems
149divePath path = magnify $ to modElems
156 = over pdElements (toListOf $ folded . diver)
152 = over pdElements (toListOf $ folded . diver)
157− . over pdPath (<> path)
153+ . over pdPath (<> DomPath (L.map _emShow path))
158 diver :: Fold Element Element
154 diver :: Fold Element Element
159− diver = foldr (.) id $ map toDive path
155+ diver = F.foldr (.) id $ L.map toDive path
160− toDive n = nodes . folded . _Element . ell n
156+ toDive n = nodes . folded . _Element . elMatch n
163 :: (Monad m, Foldable g)
159 :: (Monad m, Foldable g)
164− => Text
160+ => ElemMatcher
165 -> DomParserT [] m a
161 -> DomParserT [] m a
166 -> DomParserT g m a
162 -> DomParserT g m a
167diveElem p = divePath [p]
163diveElem p = divePath [p]
@@ -179,7 +175,7 @@ ignoreElem test parser = do
179 if ign then pure Nothing else Just <$> parser
175 if ign then pure Nothing else Just <$> parser
181-- | If current element has no children nodes does not run parser and returns
177-- | If current element has no children nodes does not run parser and returns
182−-- Nothing. Otherwise runs parser inside current element. Usefull when you got
178+-- Nothing. Otherwise runs parser inside current element. Useful when you got
183-- XML with strange empty elements which must be just ignored, but `inElem` runs
179-- XML with strange empty elements which must be just ignored, but `inElem` runs
184-- parser inside of this elements which causes to parser error.
180-- parser inside of this elements which causes to parser error.
185ignoreEmpty
181ignoreEmpty
@@ -188,7 +184,7 @@ ignoreEmpty
188 -> DomParserT Identity m (Maybe a)
184 -> DomParserT Identity m (Maybe a)
189ignoreEmpty = ignoreElem test
185ignoreEmpty = ignoreElem test
191− test e = null $ e ^. nodes
187+ test e = L.null $ e ^. nodes
193-- | If all current elements contains blank content, or contains nothing at all
189-- | If all current elements contains blank content, or contains nothing at all
194-- , then returns Nothing, else runs parser.
190-- , then returns Nothing, else runs parser.
@@ -202,114 +198,6 @@ ignoreBlank = ignoreElem test
203 elems = e ^.. nodes . folded . _Element
199 elems = e ^.. nodes . folded . _Element
204 cont = mconcat $ e ^.. nodes . folded . _Content
200 cont = mconcat $ e ^.. nodes . folded . _Content
205− in if | not $ null elems -> False
201+ in if | not $ L.null elems -> False
206 | T.null $ T.strip cont -> True
202 | T.null $ T.strip cont -> True
207 | otherwise -> False
203 | otherwise -> False
209−-- | Returns name of current element.
212−getCurrentName :: (Monad m) => DomParserT Identity m Text
213−getCurrentName = view $ pdElements . to runIdentity . localName
215−-- | If name of current tag differs from first argument throws 'PENotFound' with
216−-- tag name replaced in last path's segment. Usefull for checking root
217−-- document's element name.
221− -> DomParserT Identity m ()
222−checkCurrentName n = do
223− cn <- getCurrentName
224− unless (cn == n) $ do
226− let pinit = if null p then [] else init p
227− throwError $ ParserErrors [PENotFound $ pinit ++ [n]]
230−-- | Get current content. If current element contains no content or
231−-- have inner elements then Nothing returned
234−getCurrentContent :: (Monad m) => DomParserT Identity m (Maybe Text)
235−getCurrentContent = do
236− nds <- view $ pdElements . to runIdentity . nodes
239− els = nds ^.. folded . _Element
241− conts = nds ^.. folded . _Content
243− | not $ null els -> Nothing
244− | null conts -> Nothing
245− | otherwise -> Just $ mconcat conts
247−-- | Parses content inside current tag. It expects current element set
248−-- consists of exactly ONE element. If current element does not
249−-- contains content or have other elements as childs then throws error
252− => (Text -> Either Text a)
253− -- ^ Content parser, return error msg if value is not parsed
254− -> DomParserT Identity m a
255−parseContent parse = getCurrentContent >>= \case
256− Nothing -> throwParserError PEContentNotFound
257− Just c -> case parse c of
258− Left e -> throwParserError $ PEWrongFormat e
261−-- | If reader returns 'Nothing' then resulting function returns 'Left
268− => (Text -> Maybe a)
269− -- ^ Content or attribute reader
271− -- ^ Content or attribute value
273−maybeReadContent f t = maybe (Left msg) Right $ f t
275− msg = "Not readable " <> n <> ": " <> t
276− n = T.pack $ show $ typeRep (Proxy :: Proxy a)
278−-- | Tries to read given text to value using 'Read'. Usefull to use
279−-- with 'parseContent' and 'parseAttribute'
281− :: (Read a, Typeable a)
284−readContent = maybeReadContent $ readMaybe . T.unpack . T.strip
286−-- | Retuns map of attributes of current element
289−getCurrentAttributes :: (Monad m) => DomParserT Identity m (M.Map Name Text)
290−getCurrentAttributes = view $ pdElements . to runIdentity . attrs
292−-- | Returns element with given name or 'Nothing'
295−getCurrentAttribute :: (Monad m) => Text -> DomParserT Identity m (Maybe Text)
296−getCurrentAttribute attrName'
297− = preview $ pdElements . to runIdentity . attr attrName
299− attrName = Name attrName' Nothing Nothing
301−-- | Parses attribute with given name, throws error if attribute is not found.
308− -> (Text -> Either Text a)
309− -- ^ Attribute content parser
310− -> DomParserT Identity m a
311−parseAttribute attrName parser = getCurrentAttribute attrName >>= \case
312− Nothing -> throwParserError $ PEAttributeNotFound attrName
313− Just aval -> case parser aval of
314− Left err -> throwParserError $ PEAttributeWrongFormat attrName err