git.iko.soy/typeable/dom-parser
on f32ff9e help sign in
17 files changed+278−879
since splitsnapshots
unifiedsplit
.gitignore−2 .travis.yml−32 CHANGELOG.md−50 README.md−5 dom-parser.cabal+9−23 ghc-963.yaml−7 src/Text/XML/DOM/Parser.hs+2−10 src/Text/XML/DOM/Parser/Attributes.hs−64 src/Text/XML/DOM/Parser/Buildable.hs−26 src/Text/XML/DOM/Parser/FromDom.hs → src/Text/XML/DOM/Parser/Class.hs+80−56 src/Text/XML/DOM/Parser/Combinators.hs+98−61 src/Text/XML/DOM/Parser/Content.hs−129 src/Text/XML/DOM/Parser/FromAttribute.hs−112 src/Text/XML/DOM/Parser/Types.hs+60−159 src/Text/XML/Soap.hs−46 stack.yaml−1 test/Main.hs+29−96
.gitignoredeleted−2
@@ -1,2 +0,0 @@
1−TAGS
2−.stack-work
.travis.ymldeleted−32
@@ -1,32 +0,0 @@
1−# Use new container infrastructure to enable caching
2−sudo: false
3−
4−# Do not choose a language; we provide our own build tools.
5−language: generic
6−
7−env:
8− - STACK_YAML=stack.yaml
9− - STACK_YAML=ghc-802.yaml
10− - STACK_YAML=ghc-822.yaml
11− - STACK_YAML=ghc-841.yaml
12−
13−# Caching so the next build will be fast too.
14−cache:
15− directories:
16− - $HOME/.stack
17−
18−# Ensure necessary system libraries are present
19−addons:
20− apt:
21− packages:
22− - libgmp-dev
23−
24−before_install:
25−# Download and unpack the stack executable
26−- mkdir -p ~/.local/bin
27−- export PATH=$HOME/.local/bin:$PATH
28−- travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
29−
30−script:
31−# Build the package, its tests, and its docs and run the tests
32−- stack --no-terminal --install-ghc test --haddock --no-haddock-deps
CHANGELOG.md−50
@@ -1,54 +1,4 @@
11# CHANGELOG
22
3−## HEAD
4−
5−## 3.2.0
6−
7−### Added
8−
9−* Text.XML.Soap module with XmlEnvelope stuff in it.
10−* GHC 9 compatibility.
11−
12−### Removed
13−
14−* Old stackage files.
15−
16−## 3.1.0
17−
18−### Added
19−
20−* Additional builds with GHC 8.0.2 and 8.2.1
21−* Compatibility with GHC 8.4.1
22−
23−## 3.0.0
24−
25−* GHC 8 compatible release
26−
27−### Removed
28−
29−* `open-union` dependency
30−
31−## 2.0.0
32−
33−### Added
34−
35−* `NameMatcher` and `ElemMatcher` as more flexible mechanisms for
36− matching names and elements instead of just matching by name.
37−* Class `FromAttribute` and some default instances
38−
39−### Changed
40−
41−* `DomTraversable` renamed to `Buildable`
42−* Combinators use `NameMatcher` and `ElemMatcher`
43−* Documentation improved
44−
45−## 1.0.0
46−
47−* Added support for parsing attributes
48−* Changed public interface for 'parseContent'
49−* Added useful functions
50−* Dropped unnecessary dependency from shakespeare
51−
523## 0.1.0
53−
544* Public release
README.mddeleted−5
@@ -1,5 +0,0 @@
1−# dom-parser
2−
3−[![Build Status](https://travis-ci.org/typeable/dom-parser.svg?branch=master)](https://travis-ci.org/typeable/dom-parser)
4−
5−Simple monadic DOM parser
dom-parser.cabal+9−23
@@ -1,5 +1,5 @@
11name: dom-parser
2−version: 3.2.0
2+version: 0.1.0
33synopsis: Simple monadic DOM parser
44license: MIT
55license-file: LICENSE
@@ -7,17 +7,8 @@ author: Typeable.io contributors
77maintainer: makeit@typeable.io
88category: XML
99build-type: Simple
10−cabal-version: >=1.10
11−homepage: https://github.com/typeable/dom-parser
12−tested-with: GHC == 9.2.7
13− , GHC == 9.4.8
14− , GHC == 9.6.3
1510extra-source-files: CHANGELOG.md
16− , README.md
17−
18−source-repository head
19− type: git
20− location: git@github.com:typeable/dom-parser.git
11+cabal-version: >=1.10
2112
2213library
2314 hs-source-dirs: src
@@ -44,26 +35,21 @@ library
4435 , TypeFamilies
4536 , TypeOperators
4637 , UndecidableInstances
47− build-depends: base >= 4.11 && < 5
48− , case-insensitive
49− , containers
38+ build-depends: base >= 4.7 && < 5
5039 , lens
51− , mtl < 2.3 || > 2.3
52− , scientific
40+ , mtl
41+ , open-union
42+ , semigroups
43+ , shakespeare
5344 , text
5445 , transformers
46+ , type-fun
5547 , xml-conduit
56− , xml-conduit-writer
5748 , xml-lens
5849 exposed-modules: Text.XML.DOM.Parser
59− , Text.XML.DOM.Parser.Attributes
60− , Text.XML.DOM.Parser.Buildable
50+ , Text.XML.DOM.Parser.Class
6151 , Text.XML.DOM.Parser.Combinators
62− , Text.XML.DOM.Parser.Content
63− , Text.XML.DOM.Parser.FromAttribute
64− , Text.XML.DOM.Parser.FromDom
6552 , Text.XML.DOM.Parser.Types
66− , Text.XML.Soap
6753
6854test-suite test
6955 type: exitcode-stdio-1.0
ghc-963.yamldeleted−7
@@ -1,7 +0,0 @@
1−resolver: lts-22.6
2−
3−extra-deps:
4− - xml-conduit-writer-0.1.1.5
5−
6−nix:
7− packages: [zlib]
src/Text/XML/DOM/Parser.hs+2−10
@@ -1,17 +1,9 @@
11module Text.XML.DOM.Parser
2− ( module Text.XML.DOM.Parser.Attributes
3− , module Text.XML.DOM.Parser.Buildable
2+ ( module Text.XML.DOM.Parser.Class
43 , module Text.XML.DOM.Parser.Combinators
5− , module Text.XML.DOM.Parser.Content
6− , module Text.XML.DOM.Parser.FromAttribute
7− , module Text.XML.DOM.Parser.FromDom
84 , module Text.XML.DOM.Parser.Types
95 ) where
106
11−import Text.XML.DOM.Parser.Attributes
12−import Text.XML.DOM.Parser.Buildable
7+import Text.XML.DOM.Parser.Class
138import Text.XML.DOM.Parser.Combinators
14−import Text.XML.DOM.Parser.Content
15−import Text.XML.DOM.Parser.FromAttribute
16−import Text.XML.DOM.Parser.FromDom
179import Text.XML.DOM.Parser.Types
src/Text/XML/DOM/Parser/Attributes.hsdeleted−64
@@ -1,64 +0,0 @@
1−module Text.XML.DOM.Parser.Attributes
2− ( parseAttribute
3− , parseAttributeMaybe
4− , getCurrentAttributes
5− , getCurrentAttribute
6− ) where
7−
8−import Control.Lens
9−import Data.Map.Strict as M
10−import Data.Text as T
11−import Text.XML.DOM.Parser.Types
12−import Text.XML.Lens
13−
14−-- | Parses attribute with given name, throws error if attribute is not found.
15−--
16−-- @since 1.0.0
17−parseAttribute
18− :: (Monad m)
19− => NameMatcher
20− -- ^ Attribute name
21− -> (Text -> Either Text a)
22− -- ^ Attribute content parser
23− -> DomParserT Identity m a
24−parseAttribute attrName parser =
25− parseAttributeMaybe attrName parser >>= \case
26− Nothing -> throwParserError $ PEAttributeNotFound attrName
27− Just a -> return a
28−
29−-- | Parses attribute with given name. Returns Nothing if attribute is
30−-- not found.
31−--
32−-- @since 1.0.0
33−parseAttributeMaybe
34− :: (Monad m)
35− => NameMatcher
36− -- ^ Attribute name
37− -> (Text -> Either Text a)
38− -- ^ Attribute content parser
39− -> DomParserT Identity m (Maybe a)
40−parseAttributeMaybe attrName parser =
41− getCurrentAttribute attrName >>= \case
42− Nothing -> return Nothing
43− Just aval -> case parser aval of
44− Left err -> throwParserError $ PEAttributeWrongFormat attrName err
45− Right a -> return $ Just a
46−
47−-- | Retuns map of attributes of current element
48−--
49−-- @since 1.0.0
50−getCurrentAttributes
51− :: (Monad m)
52− => DomParserT Identity m (M.Map Name Text)
53−getCurrentAttributes = view $ pdElements . to runIdentity . attrs
54−
55−-- | Returns element with given name or 'Nothing'
56−--
57−-- @since 1.0.0
58−getCurrentAttribute
59− :: (Monad m)
60− => NameMatcher
61− -> DomParserT Identity m (Maybe Text)
62−getCurrentAttribute attrName
63− = preview $ pdElements . to runIdentity . attrs . to M.toList
64− . traversed . filtered (views _1 (attrName ^. nmMatch)) . _2
src/Text/XML/DOM/Parser/Buildable.hsdeleted−26
@@ -1,26 +0,0 @@
1−module Text.XML.DOM.Parser.Buildable
2− ( Buildable(..)
3− ) where
4−
5−import Data.Functor.Identity
6−import Data.List.NonEmpty as NE
7−import Data.Maybe
8−
9−-- | Class of traversable functors which may be constructed from list
10−class Traversable f => Buildable f where
11− -- | If method return Nothing this means we can not build
12− -- traversable from given list. In this case 'inFilteredTrav' should
13− -- fail traversing.
14− build :: [a] -> Maybe (f a)
15−
16−instance Buildable Identity where
17− build = fmap Identity . listToMaybe
18−
19−instance Buildable [] where
20− build = Just
21−
22−instance Buildable Maybe where
23− build = Just . listToMaybe
24−
25−instance Buildable NonEmpty where
26− build = NE.nonEmpty
src/Text/XML/DOM/Parser/FromDom.hs → src/Text/XML/DOM/Parser/Class.hsrenamed+80−56
@@ -1,9 +1,10 @@
1−module Text.XML.DOM.Parser.FromDom
1+module Text.XML.DOM.Parser.Class
22 ( -- * FromDom
33 FromDom(..)
44 , proxyFromDom
55 -- * Explicit methods for convenience
66 , elementFromDom
7+ , unionFromDom
78 , textFromDom
89 , stringFromDom
910 , charFromDom
@@ -14,19 +15,21 @@ module Text.XML.DOM.Parser.FromDom
1415 , boolFromDom
1516 , unitFromDom
1617 , voidFromDom
17− , scientificFromDom
1818 ) where
1919
20−import Control.Applicative
21−import Control.Lens
22−import Data.Fixed
23−import Data.Scientific
24−import Data.Text as T hiding (empty)
25−import Data.Typeable
26−import Data.Void
27−import Text.XML
28−import Text.XML.DOM.Parser.Content
29−import Text.XML.DOM.Parser.Types
20+import Control.Applicative
21+import Control.Lens
22+import Data.Fixed
23+import Data.OpenUnion
24+import Data.Text (Text)
25+import qualified Data.Text as T
26+import Data.Typeable
27+import Data.Void
28+import Text.Shakespeare.Text (st)
29+import Text.XML
30+import Text.XML.DOM.Parser.Combinators
31+import Text.XML.DOM.Parser.Types
32+import TypeFun.Data.List hiding (Union)
3033
3134proxyFromDom
3235 :: forall proxy m a
@@ -35,86 +38,107 @@ proxyFromDom
3538 -> DomParserT Identity m a
3639proxyFromDom _ = fromDom
3740
38−-- | Class of types which can be parsed from single XML element. The
39−-- method 'fromDom' is convenient default to use with 'inElem'
41+-- | Class of types which can be parsed from single XML element.
4042class FromDom a where
4143 fromDom :: (Monad m) => DomParserT Identity m a
4244
4345instance FromDom () where
4446 fromDom = unitFromDom
4547
46−-- | Always successfully parses any DOM to @()@
47−unitFromDom :: (Monad m) => DomParserT Identity m ()
48−unitFromDom = pure ()
49−
5048instance FromDom Void where
5149 fromDom = voidFromDom
5250
53−-- | Never parses successfully. It is just 'empty'
54−voidFromDom :: (Monad m) => DomParserT Identity m Void
55−voidFromDom = empty
56−
5751instance FromDom Text where
5852 fromDom = textFromDom
5953
60−textFromDom :: (Monad m) => DomParserT Identity m Text
61−textFromDom = parseContent Right
62−
6354instance FromDom String where
6455 fromDom = stringFromDom
6556
66−stringFromDom :: (Monad m) => DomParserT Identity m String
67−stringFromDom = parseContent $ Right . T.unpack
68−
6957instance FromDom Char where
7058 fromDom = charFromDom
7159
72−charFromDom :: (Monad m) => DomParserT Identity m Char
73−charFromDom = parseContent readChar
74−
7560instance FromDom Int where
7661 fromDom = intFromDom
7762
78−intFromDom :: (Monad m) => DomParserT Identity m Int
79−intFromDom = parseContent readContent
80−
8163instance FromDom Integer where
8264 fromDom = integerFromDom
8365
84−integerFromDom :: (Monad m) => DomParserT Identity m Integer
85−integerFromDom = parseContent readContent
86−
8766instance FromDom Double where
8867 fromDom = doubleFromDom
8968
90−doubleFromDom :: (Monad m) => DomParserT Identity m Double
91−doubleFromDom = parseContent readContent
92−
9369instance (HasResolution a, Typeable a) => FromDom (Fixed a) where
9470 fromDom = fixedFromDom
9571
96−fixedFromDom
97− :: (Monad m, Typeable a, HasResolution a)
98− => DomParserT Identity m (Fixed a)
99−fixedFromDom = parseContent readContent
100−
101−instance FromDom Scientific where
102− fromDom = scientificFromDom
103−
104−scientificFromDom :: Monad m => DomParserT Identity m Scientific
105−scientificFromDom = parseContent readContent
106−
10772instance FromDom Bool where
10873 fromDom = boolFromDom
10974
110−-- | Expects content to be y, yes, t, true or 1 for True Values n, no,
111−-- f, false or 0 for False. Case is not significant, blank characters
112−-- are striped.
113−boolFromDom :: (Monad m) => DomParserT Identity m Bool
114−boolFromDom = parseContent readBool
75+instance FromDom (Union '[]) where
76+ fromDom = empty
77+
78+instance ( Typeable a, FromDom a, FromDom (Union as)
79+ , SubList as (a ': as) )
80+ => FromDom (Union (a ': as)) where
81+ -- fromDom :: forall m. (DomParserMonad m) => m a
82+ fromDom = (liftUnion <$> (proxyFromDom (Proxy :: Proxy a)))
83+ <|> (reUnion <$> (proxyFromDom (Proxy :: Proxy (Union as))))
11584
11685instance FromDom Element where
11786 fromDom = elementFromDom
11887
11988elementFromDom :: (Monad m) => DomParserT Identity m Element
12089elementFromDom = view $ pdElements . to runIdentity
90+
91+unionFromDom
92+ :: (Monad m, FromDom (Union as))
93+ => proxy as
94+ -> DomParserT Identity m (Union as)
95+unionFromDom _ = fromDom
96+
97+textFromDom :: (Monad m) => DomParserT Identity m Text
98+textFromDom = parseContent pure
99+
100+stringFromDom :: (Monad m) => DomParserT Identity m String
101+stringFromDom = parseContent $ pure . T.unpack
102+
103+charFromDom :: (Monad m) => DomParserT Identity m Char
104+charFromDom = parseContent $ \t -> case T.unpack $ T.strip t of
105+ [c] -> pure c
106+ _ -> throwParserError $ PEWrongFormat
107+ "Should have exactly one non-blank character"
108+
109+intFromDom :: (Monad m) => DomParserT Identity m Int
110+intFromDom = parseContent readContent
111+
112+integerFromDom :: (Monad m) => DomParserT Identity m Integer
113+integerFromDom = parseContent readContent
114+
115+doubleFromDom :: (Monad m) => DomParserT Identity m Double
116+doubleFromDom = parseContent readContent
117+
118+fixedFromDom
119+ :: (Monad m, Typeable a, HasResolution a)
120+ => DomParserT Identity m (Fixed a)
121+fixedFromDom = parseContent readContent
122+
123+-- | Expects content to be y, yes, t, true or 1 for True value. n, no,
124+-- f, false or 0 for False value. Case is not significant, blank
125+-- characters are striped.
126+boolFromDom :: (Monad m) => DomParserT Identity m Bool
127+boolFromDom = parseContent $ \t ->
128+ let
129+ lowt = T.toLower $ T.strip t
130+ tvals = ["y", "yes", "t", "true", "1"]
131+ fvals = ["n", "no", "f", "false", "0"]
132+ in if | lowt `elem` tvals -> return True
133+ | lowt `elem` fvals -> return False
134+ | otherwise ->
135+ let msg = [st|Could not read "#{t}" as Bool|]
136+ in throwParserError $ PEWrongFormat msg
137+
138+-- | Always successfully parses any DOM to @()@
139+unitFromDom :: (Monad m) => DomParserT Identity m ()
140+unitFromDom = pure ()
141+
142+-- | Never parses successfully. It is just 'mzero'
143+voidFromDom :: (Monad m) => DomParserT Identity m Void
144+voidFromDom = empty
src/Text/XML/DOM/Parser/Combinators.hs+98−61
@@ -2,7 +2,7 @@ module Text.XML.DOM.Parser.Combinators
22 ( -- * Generic combinators to traverse descendants
33 traverseElems
44 , inFilteredTrav
5− -- * Using 'Buildable'
5+ -- * Using 'DomTraversable'
66 , inElemTrav
77 , inElem
88 , inElemAll
@@ -15,106 +15,105 @@ module Text.XML.DOM.Parser.Combinators
1515 , ignoreElem
1616 , ignoreEmpty
1717 , ignoreBlank
18+ -- * Checking current element properties
19+ , checkCurrentName
20+ -- * Parsing arbitrary content
21+ , parseContent
22+ , readContent
1823 ) where
1924
20−import Control.Lens
21−import Control.Monad.Reader
22−import Data.Foldable as F
23−import Data.List as L
24−import Data.List.NonEmpty (NonEmpty(..))
25−import Data.Text as T
26−import Data.Traversable
27−import Text.XML
28−import Text.XML.DOM.Parser.Buildable
29−import Text.XML.DOM.Parser.Types
30−import Text.XML.Lens
31−
32−
33−-- | Generic function to traverse arbitrary inner elements.
25+import Control.Lens
26+import Control.Monad.Except
27+import Control.Monad.Reader
28+import Data.List.NonEmpty (NonEmpty(..))
29+import Data.Monoid
30+import Data.Text (Text)
31+import qualified Data.Text as T
32+import Data.Traversable
33+import Data.Typeable
34+import Text.Read
35+import Text.Shakespeare.Text (st)
36+import Text.XML
37+import Text.XML.DOM.Parser.Types
38+import Text.XML.Lens
39+
40+
41+-- | Generic function to traverse arbitrary inner cursors.
3442traverseElems
3543 :: (Monad m, Foldable g, Traversable f)
36− => ([Element] -> DomParserT g m (f (DomPath, Element)))
37− -- ^ Takes list of current elements and returns container with
38− -- pairs of subpath (relatively to current elements) and element
39− -- to run parser in
44+ => ([Element] -> DomParserT g m (f ([Text], Element)))
45+ -- ^ Takes set of current elements and
4046 -> DomParserT Identity m a
41− -- ^ Parser to run for each element found in traversable 'f'
47+ -- ^ Parser will be runned for each element found in traversable
4248 -> DomParserT g m (f a)
4349traverseElems trav parser = do
4450 pd <- ask
4551 inner <- trav $ pd ^.. pdElements . folded
4652 for inner $ \(subpath, e) -> do
47− let
48− newpd = ParserData
49− { _pdElements = Identity e
50− , _pdPath = pd ^. pdPath <> subpath }
51− lift $ runReaderT parser newpd
53+ let newpd = ParserData
54+ { _pdElements = Identity e
55+ , _pdPath = pd ^. pdPath <> subpath }
56+ magnify (to $ const newpd) parser
5257
53−-- | Traverses elements located in same path using filtering function
58+-- | Takes function filtering
5459inFilteredTrav
55− :: (Monad m, Foldable g, Buildable f)
56− => ([Element] -> (DomPath, [Element]))
57− -- ^ Takes list of current elements and returns some descendants
58− -- subset and path this descendants located at. Path is should be
59− -- same for all descendants and required for error message
60+ :: (Monad m, Foldable g, DomTraversable f)
61+ => ([Element] -> ([Text], [Element]))
62+ -- ^ Function returning some filtered elements with path suffixes which will
63+ -- be appended to parser's state
6064 -> DomParserT Identity m a
6165 -> DomParserT g m (f a)
6266inFilteredTrav deeper = traverseElems trav
6367 where
6468 trav e = do
6569 let (path, elems) = deeper e
66− case build elems of
70+ case buildDomTraversable elems of
6771 Nothing -> throwParserError $ PENotFound . (<> path)
6872 Just tr -> return $ fmap (path,) tr
6973
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.
7474inElemTrav
75− :: (Monad m, Foldable g, Buildable f)
76− => ElemMatcher -- ^ Tag(s) matcher to traverse in
75+ :: (Monad m, Foldable g, DomTraversable f)
76+ => Text
7777 -> DomParserT Identity m a
7878 -> DomParserT g m (f a)
7979inElemTrav n = inFilteredTrav deeper
8080 where
81− elemsFold = folded . nodes . folded . _Element . elMatch n
82− deeper = (DomPath [_emShow n],) . toListOf elemsFold
81+ deeper = ([n],) . toListOf (folded . nodes . folded . _Element . ell n)
8382
84−-- | Runs parser inside first children element matched by macher
83+-- | Runs parser inside first children element with given name
8584inElem
8685 :: (Monad m, Foldable g)
87− => ElemMatcher
86+ => Text
8887 -> DomParserT Identity m a
8988 -> DomParserT g m a
9089inElem n = fmap runIdentity . inElemTrav n
9190
9291inElemAll
9392 :: (Monad m, Foldable g)
94− => ElemMatcher
93+ => Text
9594 -> DomParserT Identity m a
9695 -> DomParserT g m [a]
9796inElemAll = inElemTrav
9897
9998inElemMay
10099 :: (Monad m, Foldable g)
101− => ElemMatcher
100+ => Text
102101 -> DomParserT Identity m a
103102 -> DomParserT g m (Maybe a)
104103inElemMay = inElemTrav
105104
106105inElemNe
107106 :: (Monad m, Foldable g)
108− => ElemMatcher
107+ => Text
109108 -> DomParserT Identity m a
110109 -> DomParserT g m (NonEmpty a)
111110inElemNe = inElemTrav
112111
113112{- | 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
113+["a", "b"]@ differs from @inElem "a" . inElem "b"@. Namely the first variant
115114will not fail if occured tag "a" which does not contains tag "b". This
116115behaviour 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
116+to pull tags in some path. The other difference is in traversing inner
118117elements. Consider this code
119118
120119@
@@ -132,31 +131,26 @@ which translates like: @a > b > c > fromDom@
132131As you can see, inElem always takes first element and runs inner parser in this
133132single element, unlike 'divePath' which runs inner parser @in all@ descendants
134133in given path.
135−
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
139−parser.
140134-}
141135
142136divePath
143137 :: forall m g a
144138 . (Monad m, Foldable g)
145− => [ElemMatcher]
139+ => [Text]
146140 -> DomParserT [] m a
147141 -> DomParserT g m a
148142divePath path = magnify $ to modElems
149143 where
150144 modElems
151145 = over pdElements (toListOf $ folded . diver)
152− . over pdPath (<> DomPath (L.map _emShow path))
146+ . over pdPath (<> path)
153147 diver :: Fold Element Element
154− diver = F.foldr (.) id $ L.map toDive path
155− toDive n = nodes . folded . _Element . elMatch n
148+ diver = foldr (.) id $ map toDive path
149+ toDive n = nodes . folded . _Element . ell n
156150
157151diveElem
158152 :: (Monad m, Foldable g)
159− => ElemMatcher
153+ => Text
160154 -> DomParserT [] m a
161155 -> DomParserT g m a
162156diveElem p = divePath [p]
@@ -166,7 +160,7 @@ ignoreElem
166160 :: (Monad m)
167161 => (Element -> Bool)
168162 -- ^ Predicate checking that we must ignore some current tag. If returns
169− -- 'True' then parser will not be runned and combinator just returns Nothing.
163+ -- true then parser will not be runned and combinator just returns Nothing.
170164 -> DomParserT Identity m a
171165 -> DomParserT Identity m (Maybe a)
172166ignoreElem test parser = do
@@ -174,7 +168,7 @@ ignoreElem test parser = do
174168 if ign then pure Nothing else Just <$> parser
175169
176170-- | If current element has no children nodes does not run parser and returns
177−-- Nothing. Otherwise runs parser inside current element. Useful when you got
171+-- Nothing. Otherwise runs parser inside current element. Usefull when you got
178172-- XML with strange empty elements which must be just ignored, but `inElem` runs
179173-- parser inside of this elements which causes to parser error.
180174ignoreEmpty
@@ -183,7 +177,7 @@ ignoreEmpty
183177 -> DomParserT Identity m (Maybe a)
184178ignoreEmpty = ignoreElem test
185179 where
186− test e = L.null $ e ^. nodes
180+ test e = null $ e ^. nodes
187181
188182-- | If all current elements contains blank content, or contains nothing at all
189183-- , then returns Nothing, else runs parser.
@@ -197,6 +191,49 @@ ignoreBlank = ignoreElem test
197191 let
198192 elems = e ^.. nodes . folded . _Element
199193 cont = mconcat $ e ^.. nodes . folded . _Content
200− in if | not $ L.null elems -> False
194+ in if | not $ null elems -> False
201195 | T.null $ T.strip cont -> True
202196 | otherwise -> False
197+
198+-- | 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
200+-- document's element name.
201+checkCurrentName
202+ :: (Monad m)
203+ => Text
204+ -> DomParserT Identity m ()
205+checkCurrentName n = do
206+ cn <- view $ pdElements . to runIdentity . localName
207+ unless (cn == n) $ do
208+ p <- view pdPath
209+ let pinit = if null p then [] else init p
210+ throwError $ ParserErrors [PENotFound $ pinit ++ [n]]
211+ return ()
212+
213+-- | Parses content inside current tag. It expects current element set consists
214+-- of exactly ONE element. Throws error if current elements set contains
215+-- multiple of them.
216+parseContent
217+ :: (Monad m)
218+ => (Text -> DomParserT Identity m a)
219+ -> DomParserT Identity m a
220+parseContent parse = do
221+ e <- view $ pdElements . to runIdentity
222+ let
223+ nds = e ^. nodes
224+ els = nds ^.. folded . _Element
225+ conts = nds ^.. folded . _Content
226+ when (not $ null els) $ throwParserError PEContentNotFound
227+ when (null conts) $ throwParserError PEContentNotFound
228+ parse $ mconcat conts
229+
230+readContent
231+ :: forall m g a
232+ . (Read a, Typeable a, Monad m)
233+ => Text
234+ -> DomParserT g m a
235+readContent t = case readMaybe $ T.unpack t of
236+ Nothing -> throwParserError $ PEWrongFormat [st|Not readable #{n}: #{t}|]
237+ Just a -> pure a
238+ where
239+ n = show $ typeRep (Proxy :: Proxy a)
src/Text/XML/DOM/Parser/Content.hsdeleted−129
@@ -1,129 +0,0 @@
1−module Text.XML.DOM.Parser.Content
2− (
3− -- * Parsing element's content
4− parseContent
5− -- * Getting current element's properties
6− , getCurrentName
7− , getCurrentContent
8− , checkCurrentName
9− -- * Internal
10− , maybeReadContent
11− , readContent
12− , readBool
13− , readChar
14− ) where
15−
16−import Control.Lens
17−import Control.Monad
18−import Control.Monad.Except
19−import Data.List as L
20−import Data.Monoid
21−import Data.Text as T
22−import Data.Typeable
23−import Prelude as P
24−import Text.Read
25−import Text.XML.DOM.Parser.Types
26−import Text.XML.Lens
27−
28−
29−-- | Parses content inside current tag. It expects current element set
30−-- consists of exactly ONE element.
31−parseContent
32− :: (Monad m)
33− => (Text -> Either Text a)
34− -- ^ Content parser, return error msg if value is not parsed
35− -> DomParserT Identity m a
36−parseContent parse = getCurrentContent >>= \case
37− Nothing -> throwParserError PEContentNotFound
38− Just c -> case parse c of
39− Left e -> throwParserError $ PEContentWrongFormat e
40− Right a -> return a
41−
42−
43−-- | Returns name of current element.
44−--
45−-- @since 1.0.0
46−getCurrentName :: (Monad m) => DomParserT Identity m Name
47−getCurrentName = view $ pdElements . to runIdentity . name
48−
49−-- | If name of current tag differs from first argument throws 'PENotFound' with
50−-- tag name replaced in last path's segment. Useful for checking root
51−-- document's element name.
52−checkCurrentName
53− :: (Monad m)
54− => NameMatcher
55− -> DomParserT Identity m ()
56−checkCurrentName n = do
57− cn <- getCurrentName
58− unless ((n ^. nmMatch) cn) $ do
59− p <- view pdPath
60− let pinit = if L.null (unDomPath p) then [] else L.init $ unDomPath p
61− throwError $ ParserErrors [PENotFound $ DomPath $ pinit ++ [_nmShow n]]
62− return ()
63−
64−-- | Get current content. If current element contains no content or
65−-- have inner elements then Nothing returned
66−--
67−-- @since 1.0.0
68−getCurrentContent :: (Monad m) => DomParserT Identity m (Maybe Text)
69−getCurrentContent = do
70− nds <- view $ pdElements . to runIdentity . nodes
71− let
72− els :: [Element]
73− els = nds ^.. folded . _Element
74− conts :: [Text]
75− conts = nds ^.. folded . _Content
76− return $ if
77− | not $ L.null els -> Nothing
78− | L.null conts -> Nothing
79− | otherwise -> Just $ mconcat conts
80−
81−-- | If reader returns 'Nothing' then resulting function returns 'Left
82−-- "error message"'. 'Typeable' is used for generating usefull error
83−-- message.
84−--
85−-- @since 1.0.0
86−maybeReadContent
87− :: forall a
88− . (Typeable a)
89− => (Text -> Maybe a)
90− -- ^ Content or attribute reader
91− -> Text
92− -- ^ Content or attribute value
93− -> Either Text a
94−maybeReadContent f t = maybe (Left msg) Right $ f t
95− where
96− msg = "Not readable " <> n <> ": " <> t
97− n = T.pack $ P.show $ typeRep (Proxy :: Proxy a)
98−
99−
100−-- | Tries to read given text to value using 'Read'. Useful to use
101−-- with 'parseContent' and 'parseAttribute'. Content is stripped
102−-- before reading.
103−readContent
104− :: (Read a, Typeable a)
105− => Text
106− -> Either Text a
107−readContent = maybeReadContent $ readMaybe . T.unpack . T.strip
108−
109−
110−-- | @since 1.0.0
111−readBool :: Text -> Either Text Bool
112−readBool t =
113− let
114− lowt = T.toLower $ T.strip t
115− tvals = ["y", "yes", "t", "true", "1"]
116− fvals = ["n", "no", "f", "false", "0"]
117− in if
118− | lowt `L.elem` tvals -> Right True
119− | lowt `L.elem` fvals -> Right False
120− | otherwise ->
121− Left $ "Could not read " <> t <> " as Bool"
122−
123−-- | Expects text to be single character
124−--
125−-- @since 1.0.0
126−readChar :: Text -> Either Text Char
127−readChar t = case T.unpack $ T.strip t of
128− [c] -> Right c
129− _ -> Left "Should have exactly one non-blank character"
src/Text/XML/DOM/Parser/FromAttribute.hsdeleted−112
@@ -1,112 +0,0 @@
1−module Text.XML.DOM.Parser.FromAttribute
2− ( -- * FromAttribute
3− FromAttribute(..)
4− , proxyFromAttribute
5− -- * Explicit methods
6− , textFromAttribute
7− , stringFromAttribute
8− , charFromAttribute
9− , intFromAttribute
10− , integerFromAttribute
11− , doubleFromAttribute
12− , fixedFromAttribute
13− , boolFromAttribute
14− , unitFromAttribute
15− , voidFromAttribute
16− , scientificFromAttribute
17− ) where
18−
19−import Data.Fixed
20−import Data.Scientific
21−import Data.Text as T
22−import Data.Typeable
23−import Data.Void
24−import Text.XML.DOM.Parser.Content
25−
26−-- | Class of types which can be get from attribute value. Method
27−-- 'fromAttribute' is a convenient default parameter for
28−-- 'parseAttribute'
29−--
30−-- @since 2.0.0
31−class FromAttribute a where
32− fromAttribute
33− :: Text
34− -- ^ Attribute contents to parse
35− -> Either Text a
36− -- ^ Either error message or result
37−
38−proxyFromAttribute
39− :: FromAttribute a
40− => Proxy a
41− -> Text
42− -> Either Text a
43−proxyFromAttribute _ = fromAttribute
44−
45−instance FromAttribute () where
46− fromAttribute = unitFromAttribute
47−
48−unitFromAttribute :: Text -> Either Text ()
49−unitFromAttribute _ = Right ()
50−
51−instance FromAttribute Void where
52− fromAttribute = voidFromAttribute
53−
54−voidFromAttribute :: Text -> Either Text Void
55−voidFromAttribute _ = Left "Void has been parsed"
56−
57−instance FromAttribute Text where
58− fromAttribute = textFromAttribute
59−
60−textFromAttribute :: Text -> Either Text Text
61−textFromAttribute = Right
62−
63−instance FromAttribute String where
64− fromAttribute = stringFromAttribute
65−
66−stringFromAttribute :: Text -> Either Text String
67−stringFromAttribute = Right . T.unpack
68−
69−instance FromAttribute Char where
70− fromAttribute = charFromAttribute
71−
72−charFromAttribute :: Text -> Either Text Char
73−charFromAttribute = readChar
74−
75−instance FromAttribute Int where
76− fromAttribute = intFromAttribute
77−
78−intFromAttribute :: Text -> Either Text Int
79−intFromAttribute = readContent
80−
81−instance FromAttribute Integer where
82− fromAttribute = integerFromAttribute
83−
84−integerFromAttribute :: Text -> Either Text Integer
85−integerFromAttribute = readContent
86−
87−instance FromAttribute Double where
88− fromAttribute = doubleFromAttribute
89−
90−doubleFromAttribute :: Text -> Either Text Double
91−doubleFromAttribute = readContent
92−
93−instance (Typeable a, HasResolution a) => FromAttribute (Fixed a) where
94− fromAttribute = fixedFromAttribute
95−
96−fixedFromAttribute
97− :: (HasResolution a, Typeable a)
98− => Text
99− -> Either Text (Fixed a)
100−fixedFromAttribute = readContent
101−
102−instance FromAttribute Bool where
103− fromAttribute = boolFromAttribute
104−
105−boolFromAttribute :: Text -> Either Text Bool
106−boolFromAttribute = readBool
107−
108−instance FromAttribute Scientific where
109− fromAttribute = scientificFromAttribute
110−
111−scientificFromAttribute :: Text -> Either Text Scientific
112−scientificFromAttribute = readContent
src/Text/XML/DOM/Parser/Types.hs+60−159
@@ -1,182 +1,59 @@
11module Text.XML.DOM.Parser.Types
2− (-- * Element matching
3− ElemMatcher(..)
4− , emMatch
5− , emShow
6− , matchElemName
7− , elMatch
8− -- * Name matching
9− , NameMatcher(..)
10− , nmMatch
11− , nmShow
12− , matchName
13− , matchLocalName
14− , matchCILocalName
15− -- * Parser internals
16− , DomPath(..)
17− , ParserError(..)
2+ ( -- * Parser internals
3+ ParserError(..)
184 , pePath
195 , peDetails
20− , peAttributeName
216 , ParserErrors(..)
227 , _ParserErrors
238 , ParserData(..)
249 , pdElements
2510 , pdPath
11+ -- * Parser type
2612 , DomParserT
2713 , DomParser
2814 , runDomParserT
2915 , runDomParser
3016 -- * Auxiliary
17+ , DomTraversable(..)
3118 , throwParserError
19+ , throwWrongFormat
3220 ) where
3321
34−import Control.Exception
35−import Control.Lens
36−import Control.Monad.Except
37−import Control.Monad.Reader
38−import Data.CaseInsensitive as CI
39−import Data.String
40−import Data.Text as T
41−import GHC.Generics (Generic)
42−import Text.XML
43−import Text.XML.Lens
44−
45−-- | Arbitrary element matcher
46−--
47−-- @since 2.0.0
48−data ElemMatcher = ElemMatcher
49− { _emMatch :: Element -> Bool
50− , _emShow :: Text
51− -- ^ Field for 'Show' instance and bulding usefull errors
52− }
53−
54−makeLenses ''ElemMatcher
55−
56−-- | Instance using instance of 'NameMatcher'
57−instance IsString ElemMatcher where
58− fromString = matchElemName . fromString
59−
60−instance Show ElemMatcher where
61− show = T.unpack . _emShow
62−
63−-- | Match element by name
64−--
65−-- @since 2.0.0
66−matchElemName :: NameMatcher -> ElemMatcher
67−matchElemName (NameMatcher matchName showName) = ElemMatcher
68− { _emMatch = views name matchName
69− , _emShow = showName
70− }
71−
72−-- | Match over elements
73−--
74−-- @since 2.0.0
75−elMatch :: ElemMatcher -> Traversal' Element Element
76−elMatch (ElemMatcher match _) = filtered match
77−
78−-- | Arbitrary name matcher. Match name any way you want, but
79−-- considered to be used as comparator with some name with some rules
80−--
81−-- @since 2.0.0
82−data NameMatcher = NameMatcher
83− { _nmMatch :: Name -> Bool
84− -- ^ Name matching function, usually should be simple comparsion
85− -- function takin in account only local name or other components
86− -- of 'Name'
87− , _nmShow :: Text
88− -- ^ Field for 'Show' instance and bulding usefull errors
89− }
90−
91−-- | Instance use 'matchCILocalName' as most general and liberal
92−-- matching strategy (while XML is often malformed).
93−--
94−-- @since 2.0.0
95−instance IsString NameMatcher where
96− fromString = matchCILocalName . T.pack
97−
98−instance Show NameMatcher where
99− show = T.unpack . _nmShow
100−
101−-- | Makes matcher which matches only local part of name igoring
102−-- namespace and prefix. Local name matching is case sensitive.
103−--
104−-- @since 2.0.0
105−matchLocalName :: Text -> NameMatcher
106−matchLocalName tname = NameMatcher
107− { _nmMatch = \n -> nameLocalName n == tname
108− , _nmShow = tname
109− }
110−
111−-- | Makes matcher which matches only local part of name igoring
112−-- namespace and prefix. Local name matching is case insensitive. This
113−-- is the most common case.
114−--
115−-- @since 2.0.0
116−matchCILocalName :: Text -> NameMatcher
117−matchCILocalName tname = NameMatcher
118− { _nmMatch = \n -> CI.mk (nameLocalName n) == CI.mk tname
119− , _nmShow = tname
120− }
121−
122−makeLenses ''NameMatcher
123−
124−-- | Makes matcher which match name by 'Eq' with given
125−--
126−-- @since 2.0.0
127−matchName :: Name -> NameMatcher
128−matchName n = NameMatcher
129− { _nmMatch = (== n)
130− , _nmShow = nameLocalName n
131− }
132−
133−-- | Path some element should be found at. Path starts from the root
134−-- element of the document. Errors are much more usefull with path.
135−newtype DomPath = DomPath
136− { unDomPath :: [Text]
137− } deriving (Eq, Ord, Show, Semigroup, Monoid)
22+import Control.Exception
23+import Control.Lens
24+import Control.Monad.Except
25+import Control.Monad.Reader
26+import Data.List.NonEmpty (NonEmpty(..))
27+import qualified Data.List.NonEmpty as NE
28+import Data.Maybe
29+import Data.Text (Text)
30+import GHC.Generics (Generic)
31+import Text.XML
32+import Text.XML.Lens
13833
13934-- | DOM parser error description.
14035data ParserError
14136 -- | Tag not found which should be.
14237 = PENotFound
143− { _pePath :: DomPath
144− -- ^ Path of element error occured in
38+ { _pePath :: [Text]
14539 }
14640
147− -- | Expected attribute but not found
148− --
149− -- @since 1.0.0
150− | PEAttributeNotFound
151− { _peAttributeName :: NameMatcher
152− , _pePath :: DomPath
153− }
154−
155− -- | Could not parse attribute
156− --
157− -- @since 1.0.0
158− | PEAttributeWrongFormat
159− { _peAttributeName :: NameMatcher
160− , _peDetails :: Text
161− , _pePath :: DomPath
41+ -- | Tag contents has wrong format, (could not read text to value)
42+ | PEWrongFormat
43+ { _peDetails :: Text
44+ , _pePath :: [Text] -- ^ path of element
16245 }
16346
16447 -- | Node should have text content, but it does not.
16548 | PEContentNotFound
166− { _pePath :: DomPath
167− }
168−
169− -- | Tag contents has wrong format, (could not read text to value)
170− | PEContentWrongFormat
171− { _peDetails :: Text
172− , _pePath :: DomPath
49+ { _pePath :: [Text]
17350 }
17451
17552 -- | Some other error
17653 | PEOther
17754 { _peDetails :: Text
178− , _pePath :: DomPath
179− } deriving (Show, Generic)
55+ , _pePath :: [Text]
56+ } deriving (Eq, Ord, Show, Generic)
18057
18158makeLenses ''ParserError
18259
@@ -184,16 +61,15 @@ instance Exception ParserError
18461
18562newtype ParserErrors = ParserErrors
18663 { unParserErrors :: [ParserError]
187− } deriving (Show, Semigroup, Monoid, Generic)
64+ } deriving (Ord, Eq, Show, Monoid, Generic)
18865
18966makePrisms ''ParserErrors
19067
19168instance Exception ParserErrors
19269
19370
194−{- | Parser scope.
195−
196−Functor argument is usually @Identity@ or @[]@.
71+{- | Parser scope parser runs in. Functor argument is usually @Identity@ or
72+@[]@.
19773
19874If functor is @Identity@ then parser expects exactly ONE current element. This
19975is common behavior for content parsers, or parsers expecting strict XML
@@ -210,7 +86,7 @@ data ParserData f = ParserData
21086 { _pdElements :: f Element
21187 -- ^ Current element(s). Functor is intended to be either @Identity@ or
21288 -- @[]@
213− , _pdPath :: DomPath
89+ , _pdPath :: [Text]
21490 -- ^ Path for error reporting
21591 }
21692
@@ -226,11 +102,10 @@ runDomParserT
226102 -> DomParserT Identity m a
227103 -> m (Either ParserErrors a)
228104runDomParserT doc par =
229− let
230− pd = ParserData
231− { _pdElements = doc ^. root . to pure
232− , _pdPath = DomPath [doc ^. root . name . to nameLocalName]
233− }
105+ let pd = ParserData
106+ { _pdElements = doc ^. root . to pure
107+ , _pdPath = [doc ^. root . localName]
108+ }
234109 in runExceptT $ runReaderT par pd
235110
236111runDomParser
@@ -239,10 +114,36 @@ runDomParser
239114 -> Either ParserErrors a
240115runDomParser doc par = runIdentity $ runDomParserT doc par
241116
117+-- | Class of traversable functors which may be constructed from list. Or may
118+-- not.
119+class Traversable f => DomTraversable f where
120+ -- | If method return Nothing this means we can not build traversable from
121+ -- given list. In this case combinator should fail traversing.
122+ buildDomTraversable :: [a] -> Maybe (f a)
123+
124+instance DomTraversable Identity where
125+ buildDomTraversable = fmap Identity . listToMaybe
126+
127+instance DomTraversable [] where
128+ buildDomTraversable = Just
129+
130+instance DomTraversable Maybe where
131+ buildDomTraversable = Just . listToMaybe
132+
133+instance DomTraversable NonEmpty where
134+ buildDomTraversable = NE.nonEmpty
135+
242136throwParserError
243137 :: (MonadError ParserErrors m, MonadReader (ParserData f) m)
244− => (DomPath -> ParserError)
138+ => ([Text] -> ParserError)
245139 -> m a
246140throwParserError mkerr = do
247141 path <- view pdPath
248− throwError $ ParserErrors $ [mkerr path]
142+ throwError $ ParserErrors [mkerr path]
143+
144+-- | Throw 'PEWrongFormat' as very common case
145+throwWrongFormat
146+ :: (MonadError ParserErrors m, MonadReader (ParserData f) m)
147+ => Text
148+ -> m a
149+throwWrongFormat err = throwParserError $ PEWrongFormat err
src/Text/XML/Soap.hsdeleted−46
@@ -1,46 +0,0 @@
1−module Text.XML.Soap where
2−
3−import Control.Lens
4−import Data.String
5−import Text.XML
6−import Text.XML.DOM.Parser
7−import Text.XML.Writer
8−
9−data XmlEnvelope h b = XmlEnvelope
10− { _xeHeader :: !(Maybe h)
11− , _xeBody :: !(Maybe b)
12− } deriving (Eq, Show)
13−
14−makeLenses ''XmlEnvelope
15−
16−xmlEnvelopeToDocument :: (ToXML h, ToXML b) => XmlEnvelope h b -> Document
17−xmlEnvelopeToDocument xe = doc
18− where
19− sname n = fromString $ "{http://schemas.xmlsoap.org/soap/envelope/}" <> n
20− doc =
21− Document
22− { documentPrologue = Prologue [] Nothing []
23− , documentRoot =
24− Element
25− { elementName = sname "Envelope"
26− , elementAttributes = mempty
27− , elementNodes = [NodeElement header, NodeElement body]
28− }
29− , documentEpilogue = []
30− }
31− header =
32− Element
33− { elementName = sname "Header"
34− , elementAttributes = mempty
35− , elementNodes = render . toXML $ xe ^. xeHeader
36− }
37− body =
38− Element
39− { elementName = sname "Body"
40− , elementAttributes = mempty
41− , elementNodes = render . toXML $ xe ^. xeBody
42− }
43−
44−instance (FromDom h, FromDom b) => FromDom (XmlEnvelope h b) where
45− fromDom = XmlEnvelope <$> inElemMay "Header" fromDom
46− <*> inElemMay "Body" fromDom
stack.yamldeleted−1
@@ -1 +0,0 @@
1−ghc-963.yaml
test/Main.hs+29−96
@@ -27,15 +27,8 @@ testStructureFromDom = do
2727 tsBools <- inElemNe "bool" boolFromDom
2828 return TestStructure{..}
2929
30−testStructureAttributes :: (Monad m) => DomParserT Identity m TestStructure
31−testStructureAttributes = do
32− name <- getCurrentName
33− int <- parseAttribute "int" readContent
34− bool <- parseAttribute "bool" readBool
35− return $ TestStructure
36− { tsName = nameLocalName name
37− , tsInts = [int]
38− , tsBools = pure bool }
30+instance FromDom TestStructure where
31+ fromDom = testStructureFromDom
3932
4033docStruct :: Document
4134docStruct = parseText_ def [lt|
@@ -48,13 +41,6 @@ docStruct = parseText_ def [lt|
4841</root>
4942|]
5043
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−
5844docNone :: Document
5945docNone = parseText_ def [lt|
6046<?xml version="1.0" encoding="utf-8"?>
@@ -62,26 +48,8 @@ docNone = parseText_ def [lt|
6248</root>
6349|]
6450
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−
83−docSingleEmpty :: Document
84−docSingleEmpty = parseText_ def [lt|
51+docEmpty :: Document
52+docEmpty = parseText_ def [lt|
8553<?xml version="1.0" encoding="utf-8"?>
8654<root>
8755 <a/>
@@ -156,7 +124,7 @@ specParser
156124 :: String -- ^ Name of spec
157125 -> Document -- ^ Document to parse
158126 -> (a -> Maybe String) -- ^ Value checker, Nothing if all ok
159− -> DomParser Identity a -- ^ Parser itself
127+ -> DomParser Identity a -- ^ Parser itself
160128 -> Spec
161129specParser name doc check parser = it name $ do
162130 result <- either throwIO return $ runDomParser doc parser
@@ -166,9 +134,9 @@ specParser name doc check parser = it name $ do
166134
167135specParserEq
168136 :: (Eq a, Show a)
169− => String -- ^ Name of spec
170− -> Document -- ^ Document to parse
171− -> a -- ^ Value parser should return
137+ => String -- ^ Name of spec
138+ -> Document -- ^ Document to parse
139+ -> a -- ^ Value parser should return
172140 -> DomParser Identity a -- ^ Parser itself
173141 -> Spec
174142specParserEq name doc a parser = specParser name doc check parser
@@ -193,7 +161,7 @@ specParserFailedPath
193161 :: (Show a)
194162 => String -- ^ Name of spec
195163 -> Document
196− -> DomPath -- ^ Expteced path in error
164+ -> [Text] -- ^ Expteced path in error
197165 -> DomParser Identity a
198166 -> Spec
199167specParserFailedPath name doc path parser = it name $ example $ do
@@ -208,37 +176,13 @@ specParserFailedPath name doc path parser = it name $ example $ do
208176combinationsSpec :: Spec
209177combinationsSpec = do
210178 describe "inElem" $ do
179+ let parser = inElem "a" textFromDom
211180 describe "succeeds" $ do
212− describe "simple" $ do
213− let parser = inElem "a" textFromDom
214− specParserEq "docSimple" docSimple "content" 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
221− describe "fails" $ do
222− describe "simple" $ do
223− let parser = inElem "a" textFromDom
224− specParserFailed "docNone" docNone parser
225− specParserFailed "docSingleEmpty" docSingleEmpty parser
226− describe "deep" $ do
227− let parser = inElem "a" $ inElem "b" textFromDom
228− specParserFailedPath "docDeepMultiple2" docDeepMultiple2
229− (DomPath ["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
181+ specParserEq "docSimple" docSimple "content" parser
182+ specParserEq "docMultiple" docMultiple "content1" parser
240183 describe "fails" $ do
241− specParserFailed "docNone" docNone $ parseAttribute "attr" Right
184+ specParserFailed "docNone" docNone parser
185+ specParserFailed "docEmpty" docEmpty parser
242186
243187 describe "inElemAll" $ do
244188 let parser = inElemAll "a" textFromDom
@@ -248,7 +192,7 @@ combinationsSpec = do
248192 specParserEq "docMultiple" docMultiple
249193 ["content1", "content2", "content3"] parser
250194 describe "fails" $ do
251− specParserFailed "docSingleEmpty" docSingleEmpty parser
195+ specParserFailed "docEmpty" docEmpty parser
252196
253197 describe "inElemMay" $ do
254198 let parser = inElemMay "a" textFromDom
@@ -257,7 +201,7 @@ combinationsSpec = do
257201 specParserEq "docSimple" docSimple (Just "content") parser
258202 specParserEq "docMultiple" docMultiple (Just "content1") parser
259203 describe "fails" $ do
260− specParserFailed "docSingleEmpty" docSingleEmpty parser
204+ specParserFailed "docEmpty" docEmpty parser
261205
262206 describe "inElemNe" $ do
263207 let parser = inElemNe "a" textFromDom
@@ -267,7 +211,7 @@ combinationsSpec = do
267211 (NE.fromList ["content1", "content2", "content3"]) parser
268212 describe "fails" $ do
269213 specParserFailed "docNone" docNone parser
270− specParserFailed "docSingleEmpty" docSingleEmpty parser
214+ specParserFailed "docEmpty" docEmpty parser
271215
272216 describe "diveElem" $ do
273217 describe "single element" $ do
@@ -276,7 +220,7 @@ combinationsSpec = do
276220 specParserEq "docDeepMultiple1" docDeepMultiple1 "content1" parser
277221 specParserEq "docDeepMultiple2" docDeepMultiple2 "content1" parser
278222 describe "fails" $ do
279− specParserFailedPath "docSimple" docSimple (DomPath ["root", "a", "b"]) parser
223+ specParserFailedPath "docSimple" docSimple ["root", "a", "b"] parser
280224 describe "multiple elements" $ do
281225 let
282226 parser = diveElem "a" $ inElemAll "b" textFromDom
@@ -290,7 +234,7 @@ combinationsSpec = do
290234 describe "mandatory element" $ do
291235 let parser = inElem "a" $ ignoreEmpty textFromDom
292236 describe "succeeds" $ do
293− specParserEq "docSingleEmpty" docSingleEmpty Nothing parser
237+ specParserEq "docEmpty" docEmpty Nothing parser
294238 specParserEq "docSimple" docSimple (Just "content") parser
295239 specParserEq "docMultiple" docMultiple (Just "content1") parser
296240 describe "fails" $ do
@@ -299,7 +243,7 @@ combinationsSpec = do
299243 let parser = fmap join $ inElemMay "a" $ ignoreEmpty textFromDom
300244 describe "succeeds" $ do
301245 specParserEq "docNone" docNone Nothing parser
302− specParserEq "docSingleEmpty" docSingleEmpty Nothing parser
246+ specParserEq "docEmpty" docEmpty Nothing parser
303247 specParserEq "docSimple" docSimple (Just "content") parser
304248 specParserEq "docMultiple" docMultiple (Just "content1") parser
305249
@@ -311,9 +255,9 @@ combinationsSpec = do
311255 inElem "a" $ do
312256 checkCurrentName "a"
313257 describe "fails" $ do
314− specParserFailedPath "root element" docSimple (DomPath ["toor"]) $ do
258+ specParserFailedPath "root element" docSimple ["toor"] $ do
315259 checkCurrentName "toor"
316− specParserFailedPath "inner element" docSimple (DomPath ["root", "b"]) $ do
260+ specParserFailedPath "inner element" docSimple ["root", "b"] $ do
317261 inElem "a" $ checkCurrentName "b"
318262
319263contentSpec :: Spec
@@ -327,25 +271,14 @@ contentSpec = do
327271structSpec :: Spec
328272structSpec = do
329273 describe "succeeds" $ do
330− describe "dom structure" $ do
331− let
332− result = TestStructure
333− { tsName = "name"
334− , tsInts = [1,2]
335− , tsBools = pure True }
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
274+ let
275+ result = TestStructure
276+ { tsName = "name"
277+ , tsInts = [1,2]
278+ , tsBools = pure True }
279+ specParserEq "docStruct" docStruct result fromDom
344280 describe "fails" $ do
345− describe "dom structure" $ do
346− specParserFailed "docSimple" docSimple testStructureFromDom
347− describe "attr structure" $ do
348− specParserFailed "docNone" docNone testStructureAttributes
281+ specParserFailed "docSimple" docSimple testStructureFromDom
349282
350283main :: IO ()
351284main = hspec $ do