@@ -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 'Buildable'
5+ -- * Using 'DomTraversable'
6 , inElemTrav
6 , inElemTrav
8 , inElemAll
8 , inElemAll
@@ -15,30 +15,45 @@ 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
27+ -- * Parsing attributes
29+ , parseAttributeMaybe
20import Control.Lens
32import Control.Lens
33+import Control.Monad.Except
21import Control.Monad.Reader
34import Control.Monad.Reader
22import Data.Foldable as F
35import Data.Foldable as F
23import Data.List as L
36import Data.List as L
24import Data.List.NonEmpty (NonEmpty(..))
37import Data.List.NonEmpty (NonEmpty(..))
38+import Data.Map.Strict as M
25import Data.Text as T
40import Data.Text as T
26import Data.Traversable
41import Data.Traversable
27import Text.XML
42import Text.XML
28−import Text.XML.DOM.Parser.Buildable
43+import Text.XML.DOM.Parser.Common
29import Text.XML.DOM.Parser.Types
44import Text.XML.DOM.Parser.Types
30import Text.XML.Lens
45import Text.XML.Lens
33−-- | Generic function to traverse arbitrary inner elements.
48+-- | Generic function to traverse arbitrary inner cursors.
34traverseElems
49traverseElems
35 :: (Monad m, Foldable g, Traversable f)
50 :: (Monad m, Foldable g, Traversable f)
36 => ([Element] -> DomParserT g m (f (DomPath, Element)))
51 => ([Element] -> DomParserT g m (f (DomPath, Element)))
37 -- ^ Takes list of current elements and returns container with
52 -- ^ Takes list of current elements and returns container with
38− -- pairs of subpath (relatively to current elements) and element
53+ -- pairs of subpath (relatively to current element) and element
39 -- to run parser in
54 -- to run parser in
40 -> DomParserT Identity m a
55 -> DomParserT Identity m a
41− -- ^ Parser to run for each element found in traversable 'f'
56+ -- ^ Parser will be runned for each element found in traversable
42 -> DomParserT g m (f a)
57 -> DomParserT g m (f a)
43traverseElems trav parser = do
58traverseElems trav parser = do
@@ -48,73 +63,68 @@ traverseElems trav parser = do
48 newpd = ParserData
63 newpd = ParserData
49 { _pdElements = Identity e
64 { _pdElements = Identity e
50 , _pdPath = pd ^. pdPath <> subpath }
65 , _pdPath = pd ^. pdPath <> subpath }
51− lift $ runReaderT parser newpd
66+ -- type of reader has been changed, local won't work here
67+ magnify (to $ const newpd) parser
53−-- | Traverses elements located in same path using filtering function
69+-- | Takes function filtering
54inFilteredTrav
70inFilteredTrav
55− :: (Monad m, Foldable g, Buildable f)
71+ :: (Monad m, Foldable g, DomTraversable f)
56 => ([Element] -> (DomPath, [Element]))
72 => ([Element] -> (DomPath, [Element]))
57− -- ^ Takes list of current elements and returns some descendants
73+ -- ^ Function returning some filtered elements with path suffixes which will
58− -- subset and path this descendants located at. Path is should be
74+ -- be appended to parser's state
59− -- same for all descendants and required for error message
60 -> DomParserT Identity m a
75 -> DomParserT Identity m a
61 -> DomParserT g m (f a)
76 -> DomParserT g m (f a)
62inFilteredTrav deeper = traverseElems trav
77inFilteredTrav deeper = traverseElems trav
64 trav e = do
79 trav e = do
65 let (path, elems) = deeper e
80 let (path, elems) = deeper e
66− case build elems of
81+ case buildDomTraversable elems of
67 Nothing -> throwParserError $ PENotFound . (<> path)
82 Nothing -> throwParserError $ PENotFound . (<> path)
68 Just tr -> return $ fmap (path,) tr
83 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.
75− :: (Monad m, Foldable g, Buildable f)
86+ :: (Monad m, Foldable g, DomTraversable f)
76− => ElemMatcher -- ^ Tag(s) matcher to traverse in
87+ => NameMatcher -- ^ Name of tag to traverse in
77 -> DomParserT Identity m a
88 -> DomParserT Identity m a
78 -> DomParserT g m (f a)
89 -> DomParserT g m (f a)
79inElemTrav n = inFilteredTrav deeper
90inElemTrav n = inFilteredTrav deeper
81− elemsFold = folded . nodes . folded . _Element . elMatch n
82− deeper = (DomPath [_emShow n],) . toListOf elemsFold
92+ deeper = (DomPath [_nmShow n],) . toListOf (folded . nodes . folded . _Element . elMatchName n)
84−-- | Runs parser inside first children element matched by macher
94+-- | Runs parser inside first children element with given name
86 :: (Monad m, Foldable g)
96 :: (Monad m, Foldable g)
87− => ElemMatcher
97+ => NameMatcher
88 -> DomParserT Identity m a
98 -> DomParserT Identity m a
89 -> DomParserT g m a
99 -> DomParserT g m a
90inElem n = fmap runIdentity . inElemTrav n
100inElem n = fmap runIdentity . inElemTrav n
93 :: (Monad m, Foldable g)
103 :: (Monad m, Foldable g)
94− => ElemMatcher
104+ => NameMatcher
95 -> DomParserT Identity m a
105 -> DomParserT Identity m a
96 -> DomParserT g m [a]
106 -> DomParserT g m [a]
97inElemAll = inElemTrav
107inElemAll = inElemTrav
100 :: (Monad m, Foldable g)
110 :: (Monad m, Foldable g)
101− => ElemMatcher
111+ => NameMatcher
102 -> DomParserT Identity m a
112 -> DomParserT Identity m a
103 -> DomParserT g m (Maybe a)
113 -> DomParserT g m (Maybe a)
104inElemMay = inElemTrav
114inElemMay = inElemTrav
107 :: (Monad m, Foldable g)
117 :: (Monad m, Foldable g)
108− => ElemMatcher
118+ => NameMatcher
109 -> DomParserT Identity m a
119 -> DomParserT Identity m a
110 -> DomParserT g m (NonEmpty a)
120 -> DomParserT g m (NonEmpty a)
111inElemNe = inElemTrav
121inElemNe = inElemTrav
113{- | Dive given parser's current tags set into the given path. The @divePath
123{- | Dive given parser's current tags set into the given path. The @divePath
114−["a", "b"]@ differs from @inElem "a" $ inElem "b"@. Namely the first variant
124+["a", "b"]@ differs from @inElem "a" . inElem "b"@. Namely the first variant
115will not fail if occured tag "a" which does not contains tag "b". This
125will not fail if occured tag "a" which does not contains tag "b". This
116behaviour is desireable when you dont want to parse whole XML and just want
126behaviour is desireable when you dont want to parse whole XML and just want
117−to pull tags located in some path. The other difference is in traversing inner
127+to pull tags in some path. The other difference is in traversing inner
118elements. Consider this code
128elements. Consider this code
@@ -132,31 +142,26 @@ which translates like: @a > b > c > fromDom@
132As you can see, inElem always takes first element and runs inner parser in this
142As you can see, inElem always takes first element and runs inner parser in this
133single element, unlike 'divePath' which runs inner parser @in all@ descendants
143single element, unlike 'divePath' which runs inner parser @in all@ descendants
134in given path.
144in 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
143 :: forall m g a
148 :: forall m g a
144 . (Monad m, Foldable g)
149 . (Monad m, Foldable g)
145− => [ElemMatcher]
150+ => [NameMatcher]
146 -> DomParserT [] m a
151 -> DomParserT [] m a
147 -> DomParserT g m a
152 -> DomParserT g m a
148divePath path = magnify $ to modElems
153divePath path = magnify $ to modElems
151 = over pdElements (toListOf $ folded . diver)
156 = over pdElements (toListOf $ folded . diver)
152− . over pdPath (<> DomPath (L.map _emShow path))
157+ . over pdPath (<> DomPath (L.map _nmShow path))
153 diver :: Fold Element Element
158 diver :: Fold Element Element
154 diver = F.foldr (.) id $ L.map toDive path
159 diver = F.foldr (.) id $ L.map toDive path
155− toDive n = nodes . folded . _Element . elMatch n
160+ toDive n = nodes . folded . _Element . elMatchName n
158 :: (Monad m, Foldable g)
163 :: (Monad m, Foldable g)
159− => ElemMatcher
164+ => NameMatcher
160 -> DomParserT [] m a
165 -> DomParserT [] m a
161 -> DomParserT g m a
166 -> DomParserT g m a
162diveElem p = divePath [p]
167diveElem p = divePath [p]
@@ -197,6 +202,109 @@ ignoreBlank = ignoreElem test
198 elems = e ^.. nodes . folded . _Element
203 elems = e ^.. nodes . folded . _Element
199 cont = mconcat $ e ^.. nodes . folded . _Content
204 cont = mconcat $ e ^.. nodes . folded . _Content
200− in if | not $ L.null elems -> False
205+ in if | not $ L.null elems -> False
201 | T.null $ T.strip cont -> True
206 | T.null $ T.strip cont -> True
202 | otherwise -> False
207 | otherwise -> False
209+-- | Returns name of current element.
212+getCurrentName :: (Monad m) => DomParserT Identity m Name
213+getCurrentName = view $ pdElements . to runIdentity . name
215+-- | If name of current tag differs from first argument throws 'PENotFound' with
216+-- tag name replaced in last path's segment. Useful for checking root
217+-- document's element name.
221+ -> DomParserT Identity m ()
222+checkCurrentName n = do
223+ cn <- getCurrentName
224+ unless ((n ^. nmMatch) cn) $ do
226+ let pinit = if L.null (unDomPath p) then [] else L.init $ unDomPath p
227+ throwError $ ParserErrors [PENotFound $ DomPath $ pinit ++ [_nmShow 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 $ L.null els -> Nothing
244+ | L.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+-- | Retuns map of attributes of current element
266+ => DomParserT Identity m (M.Map Name Text)
267+getCurrentAttributes = view $ pdElements . to runIdentity . attrs
269+-- | Returns element with given name or 'Nothing'
275+ -> DomParserT Identity m (Maybe Text)
276+getCurrentAttribute attrName
277+ = preview $ pdElements . to runIdentity . attrs . to M.toList
278+ . traversed . filtered (views _1 (attrName ^. nmMatch)) . _2
280+-- | Parses attribute with given name, throws error if attribute is not found.
287+ -> (Text -> Either Text a)
288+ -- ^ Attribute content parser
289+ -> DomParserT Identity m a
290+parseAttribute attrName parser =
291+ parseAttributeMaybe attrName parser >>= \case
292+ Nothing -> throwParserError $ PEAttributeNotFound attrName
295+-- | Parses attribute with given name.
302+ -> (Text -> Either Text a)
303+ -- ^ Attribute content parser
304+ -> DomParserT Identity m (Maybe a)
305+parseAttributeMaybe attrName parser =
306+ getCurrentAttribute attrName >>= \case
307+ Nothing -> return Nothing
308+ Just aval -> case parser aval of
309+ Left err -> throwParserError $ PEAttributeWrongFormat attrName err
310+ Right a -> return $ Just a