xml-parser (empty) → 0.1
raw patch · 14 files changed
+1268/−0 lines, 14 filesdep +QuickCheckdep +attoparsecdep +basesetup-changed
Dependencies added: QuickCheck, attoparsec, base, bytestring, containers, hashable, quickcheck-instances, rerebase, tasty, tasty-hunit, tasty-quickcheck, text, text-builder, transformers, unordered-containers, xml-conduit, xml-parser
Files
- LICENSE +22/−0
- Setup.hs +2/−0
- library/XmlParser.hs +72/−0
- library/XmlParser/AstParser.hs +495/−0
- library/XmlParser/ElementDestructionState.hs +59/−0
- library/XmlParser/NameMap.hs +106/−0
- library/XmlParser/NamespaceRegistry.hs +82/−0
- library/XmlParser/NodeConsumerState.hs +45/−0
- library/XmlParser/Prelude.hs +90/−0
- library/XmlParser/TupleHashMap.hs +55/−0
- library/XmlParser/XmlConduitWrapper.hs +44/−0
- library/XmlParser/XmlSchemaAttoparsec.hs +74/−0
- test/Main.hs +58/−0
- xml-parser.cabal +64/−0
+ LICENSE view
@@ -0,0 +1,22 @@+Copyright (c) 2021 Nikita Volkov++Permission is hereby granted, free of charge, to any person+obtaining a copy of this software and associated documentation+files (the "Software"), to deal in the Software without+restriction, including without limitation the rights to use,+copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the+Software is furnished to do so, subject to the following+conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ library/XmlParser.hs view
@@ -0,0 +1,72 @@+module XmlParser+ ( -- * Execution+ parseByteString,+ parseLazyByteString,+ parseFile,+ parseElementAst,++ -- * Parsers by context++ -- ** Element+ AstParser.Element,+ AstParser.elementName,+ AstParser.elementNameIs,+ AstParser.children,+ AstParser.childrenByName,+ AstParser.attributesByName,+ AstParser.astElement,++ -- ** Nodes+ AstParser.Nodes,+ AstParser.elementNode,+ AstParser.contentNode,++ -- ** ByName+ AstParser.ByName,+ AstParser.byName,++ -- ** Content+ AstParser.Content,+ AstParser.textContent,+ AstParser.narrowedContent,+ AstParser.refinedContent,+ AstParser.enumContent,+ AstParser.attoparsedContent,+ AstParser.qNameContent,+ )+where++import qualified Data.ByteString.Lazy as Lbs+import qualified Text.XML as XmlConduit+import qualified XmlParser.AstParser as AstParser+import XmlParser.Prelude+import qualified XmlParser.XmlConduitWrapper as XmlConduitWrapper++-- |+-- Parse XML bytestring.+parseByteString :: AstParser.Element a -> ByteString -> Either Text a+parseByteString astParser input =+ XmlConduitWrapper.parseByteString input >>= parseDocumentAst astParser++-- |+-- Parse XML lazy bytestring.+parseLazyByteString :: AstParser.Element a -> Lbs.ByteString -> Either Text a+parseLazyByteString astParser input =+ XmlConduitWrapper.parseLazyByteString input >>= parseDocumentAst astParser++-- |+-- Parse XML file.+parseFile :: AstParser.Element a -> FilePath -> IO (Either Text a)+parseFile astParser path =+ fmap (>>= parseDocumentAst astParser) $+ XmlConduitWrapper.parseFile path++parseDocumentAst :: AstParser.Element a -> XmlConduit.Document -> Either Text a+parseDocumentAst astParser =+ parseElementAst astParser . XmlConduit.documentRoot++-- |+-- Parse an \"xml-conduit\" element AST.+parseElementAst :: AstParser.Element a -> XmlConduit.Element -> Either Text a+parseElementAst astParser =+ first AstParser.renderElementError . AstParser.parseElement astParser
+ library/XmlParser/AstParser.hs view
@@ -0,0 +1,495 @@+module XmlParser.AstParser+ ( -- * Execution+ parseElement,+ renderElementError,+ ElementError (..),+ NodeType (..),++ -- * Parsers by context++ -- ** Element+ Element,+ elementName,+ elementNameIs,+ children,+ childrenByName,+ attributesByName,+ astElement,++ -- ** Nodes+ Nodes,+ elementNode,+ contentNode,++ -- ** ByName+ ByName,+ byName,++ -- ** Content+ Content,+ textContent,+ narrowedContent,+ refinedContent,+ enumContent,+ attoparsedContent,+ qNameContent,+ )+where++import qualified Data.Attoparsec.Text as Attoparsec+import qualified Data.HashMap.Strict as HashMap+import qualified Data.List as List+import qualified Text.Builder as Tb+import qualified Text.XML as Xml+import qualified XmlParser.ElementDestructionState as ElementDestructionState+import qualified XmlParser.NameMap as NameMap+import qualified XmlParser.NamespaceRegistry as NamespaceRegistry+import qualified XmlParser.NodeConsumerState as NodeConsumerState+import XmlParser.Prelude+import qualified XmlParser.XmlSchemaAttoparsec as XmlSchemaAttoparsec++-- |+-- Parse an \"xml-conduit\" element AST.+parseElement :: Element a -> Xml.Element -> Either ElementError a+parseElement (Element run) element =+ run+ (NamespaceRegistry.interpretAttributes (Xml.elementAttributes element) NamespaceRegistry.new)+ element+ ElementDestructionState.new+ & fmap fst++renderElementError :: ElementError -> Text+renderElementError =+ Tb.run . (\(a, b) -> "/" <> Tb.intercalate "/" (reverse a) <> ": " <> b) . simplifyElementError++simplifyElementError :: ElementError -> ([Tb.Builder], Tb.Builder)+simplifyElementError =+ elementError []+ where+ sortedList renderer =+ mappend "[" . flip mappend "]" . Tb.intercalate ", " . fmap renderer . sort+ name a b =+ case a of+ Just _ -> Tb.text b+ Nothing -> Tb.text b+ elementError collectedPath = \case+ NoneOfChildrenFoundByNameElementError a b ->+ ( collectedPath,+ "None of following child element names found: "+ <> sortedList (uncurry name) a+ <> ". Names available: "+ <> sortedList (uncurry name) b+ )+ ChildByNameElementError a b c ->+ elementError (name a b : collectedPath) c+ ChildAtOffsetElementError a b ->+ nodeError (Tb.decimal a : collectedPath) b+ AttributeByNameElementError a b c ->+ (("@" <> name a b) : collectedPath, maybeContentError c)+ NoneOfAttributesFoundByNameElementError a b ->+ ( collectedPath,+ "Found none of the following attributes: " <> sortedList (uncurry name) a+ <> ". The following are available: "+ <> sortedList (uncurry name) b+ )+ NameElementError a ->+ (collectedPath, Tb.text a)+ UserElementError a ->+ (collectedPath, Tb.text a)+ nodeError collectedPath = \case+ UnexpectedNodeTypeNodeError a b ->+ ( collectedPath,+ "Unexpected node type. Got " <> nodeType b <> ", but expected " <> nodeType a+ )+ NotAvailableNodeError ->+ (collectedPath, "No nodes left")+ ElementNodeError a ->+ elementError collectedPath a+ TextNodeError a ->+ (collectedPath, maybeContentError a)+ nodeType = \case+ ElementNodeType -> "element"+ InstructionNodeType -> "instruction"+ ContentNodeType -> "content"+ CommentNodeType -> "comment"+ maybeContentError = maybe "Empty alternative" contentError+ contentError = \case+ UserContentError a ->+ Tb.text a+ ParsingContentError a ->+ Tb.text a+ NamespaceNotFoundContentError a ->+ "Namespace not found: " <> Tb.text a+ UnexpectedValueContentError a ->+ "Unexpected value: " <> Tb.text a+ EnumContentError a b ->+ "Unexpected value: " <> Tb.text b <> ". Expecting one of the following: " <> sortedList Tb.text a++-- |+-- Error in the context of an element.+--+-- It has a tree structure specifying the context of containing operations.+data ElementError+ = AttributeByNameElementError+ (Maybe Text)+ Text+ (Maybe ContentError)+ | NoneOfAttributesFoundByNameElementError+ [(Maybe Text, Text)]+ -- ^ Not found.+ [(Maybe Text, Text)]+ -- ^ Out of.+ | NoneOfChildrenFoundByNameElementError+ [(Maybe Text, Text)]+ -- ^ Not found.+ [(Maybe Text, Text)]+ -- ^ Out of.+ | ChildByNameElementError+ (Maybe Text)+ -- ^ Namespace.+ Text+ -- ^ Name.+ ElementError+ -- ^ Reason. Not 'NodeError' because only element nodes can be looked up by name.+ | ChildAtOffsetElementError+ Int+ -- ^ Offset.+ NodeError+ -- ^ Reason.+ | NameElementError Text+ | -- | Error raised by the user of this library.+ UserElementError Text++data NodeError+ = UnexpectedNodeTypeNodeError+ NodeType+ -- ^ Expected.+ NodeType+ -- ^ Actual.+ | NotAvailableNodeError+ | ElementNodeError ElementError+ | TextNodeError (Maybe ContentError)++data ContentError+ = ParsingContentError Text+ | NamespaceNotFoundContentError Text+ | UnexpectedValueContentError Text+ | EnumContentError+ [Text]+ -- ^ List of expected values.+ Text+ -- ^ Actual value+ | UserContentError Text++data NodeType+ = ElementNodeType+ | InstructionNodeType+ | ContentNodeType+ | CommentNodeType++-- |+-- Parse in the context of an element node.+newtype Element a+ = Element+ ( NamespaceRegistry.NamespaceRegistry ->+ Xml.Element ->+ ElementDestructionState.ElementDestructionState ->+ Either ElementError (a, ElementDestructionState.ElementDestructionState)+ )+ deriving+ (Functor, Applicative, Monad)+ via ( ReaderT+ (NamespaceRegistry.NamespaceRegistry)+ ( ReaderT+ Xml.Element+ ( StateT+ ElementDestructionState.ElementDestructionState+ (Except ElementError)+ )+ )+ )++instance MonadFail Element where+ fail = fromString >>> UserElementError >>> Left >>> const >>> const >>> const >>> Element++-- |+-- Parse namespace and name with the given function.+elementName :: (Maybe Text -> Text -> Either Text a) -> Element a+elementName parse =+ Element $ \nreg (Xml.Element name _ _) state ->+ fmap (,state) $ case NamespaceRegistry.resolveElementName name nreg of+ Nothing -> Left (NameElementError ("Unresolvable name: " <> fromString (show name)))+ Just (ns, name) -> parse ns name & first NameElementError++-- |+-- Fail if the namespace and name don't match the provided.+elementNameIs :: Maybe Text -> Text -> Element ()+elementNameIs ns name =+ elementName $ \actualNs actualName ->+ if actualNs == ns+ then+ if actualName == name+ then Right ()+ else Left ("Unexpected name: \"" <> actualName <> "\". Expecting: \"" <> name <> "\"")+ else Left ("Unexpected namespace: \"" <> (fromString . show) actualNs <> "\". Expecting: \"" <> (fromString . show) ns <> "\"")++-- |+-- Look up elements by name and parse them.+childrenByName :: ByName Element a -> Element a+childrenByName (ByName runByName) =+ Element $ \nreg element@(Xml.Element _ attributes _) state ->+ case ElementDestructionState.resolveChildNames (ElementDestructionState.ElementDestructionContext nreg element) state of+ (nameMap, state) ->+ case runByName nameMap (\element (Element run) -> fmap fst (run deeperNreg element ElementDestructionState.new)) of+ OkByNameResult _ res -> Right (res, state)+ NotFoundByNameResult unfoundNames ->+ let availNames = NameMap.extractNames nameMap+ in Left (NoneOfChildrenFoundByNameElementError unfoundNames availNames)+ FailedDeeperByNameResult ns name err ->+ Left (ChildByNameElementError ns name err)+ where+ deeperNreg = NamespaceRegistry.interpretAttributes attributes nreg++-- |+-- Look up the last attribute by name and parse it.+attributesByName :: ByName Content a -> Element a+attributesByName (ByName runByName) =+ Element $ \nreg element state ->+ case ElementDestructionState.resolveAttributeNames (ElementDestructionState.ElementDestructionContext nreg element) state of+ (nameMap, state) -> case runByName nameMap (\content (Content parseContent) -> parseContent (\ns -> NamespaceRegistry.lookup ns nreg) content) of+ OkByNameResult _ res -> Right (res, state)+ NotFoundByNameResult unfoundNames ->+ let availNames = NameMap.extractNames nameMap+ in Left (NoneOfAttributesFoundByNameElementError unfoundNames availNames)+ FailedDeeperByNameResult ns name err ->+ Left (AttributeByNameElementError ns name err)++-- |+-- Children sequence by order.+children :: Nodes a -> Element a+children (Nodes runNodes) =+ Element $ \nreg (Xml.Element _ _ nodes) state ->+ runNodes (NodeConsumerState.new nodes nreg)+ & fmap fst+ & fmap (,state)++-- |+-- Expose the element's AST.+astElement :: Element Xml.Element+astElement =+ Element $ \_ element state -> Right (element, state)++-- |+-- Parser in the context of a sequence of nodes.+newtype Nodes a+ = Nodes (NodeConsumerState.NodeConsumerState -> Either ElementError (a, NodeConsumerState.NodeConsumerState))+ deriving+ (Functor, Applicative, Monad)+ via (StateT NodeConsumerState.NodeConsumerState (Either ElementError))++-- |+-- Consume the next node expecting it to be element and parse its contents.+elementNode :: Element a -> Nodes a+elementNode (Element runElement) =+ Nodes $ \x ->+ case NodeConsumerState.fetchNode x of+ Just (node, x) -> case node of+ Xml.NodeElement element ->+ bimap+ (ChildAtOffsetElementError (NodeConsumerState.getOffset x) . ElementNodeError)+ (,NodeConsumerState.bumpOffset x)+ (fmap fst (runElement (NodeConsumerState.getNamespaceRegistry x) element ElementDestructionState.new))+ Xml.NodeInstruction _ -> failWithUnexpectedNodeType InstructionNodeType+ Xml.NodeContent _ -> failWithUnexpectedNodeType ContentNodeType+ Xml.NodeComment _ -> failWithUnexpectedNodeType CommentNodeType+ where+ failWithUnexpectedNodeType actualType =+ Left+ ( ChildAtOffsetElementError+ (NodeConsumerState.getOffset x)+ (UnexpectedNodeTypeNodeError ElementNodeType actualType)+ )+ _ -> Left (ChildAtOffsetElementError (NodeConsumerState.getOffset x) NotAvailableNodeError)++-- |+-- Consume the next node expecting it to be textual and parse its contents.+contentNode :: Content content -> Nodes content+contentNode (Content parseContent) =+ Nodes $ \x ->+ case NodeConsumerState.fetchNode x of+ Just (node, x) -> case node of+ Xml.NodeContent content ->+ case parseContent (\ns -> NodeConsumerState.lookupNamespace ns x) content of+ Right parsedContent ->+ Right (parsedContent, NodeConsumerState.bumpOffset x)+ Left contentError ->+ Left+ ( ChildAtOffsetElementError+ (NodeConsumerState.getOffset x)+ (TextNodeError contentError)+ )+ Xml.NodeElement _ -> failWithUnexpectedNodeType ElementNodeType+ Xml.NodeInstruction _ -> failWithUnexpectedNodeType InstructionNodeType+ Xml.NodeComment _ -> failWithUnexpectedNodeType CommentNodeType+ where+ failWithUnexpectedNodeType actualType =+ Left+ ( ChildAtOffsetElementError+ (NodeConsumerState.getOffset x)+ (UnexpectedNodeTypeNodeError ContentNodeType actualType)+ )+ _ -> Left (ChildAtOffsetElementError (NodeConsumerState.getOffset x) NotAvailableNodeError)++-- * Content++-- |+-- Parser in the context of decoded textual content,+-- which can be the value of an attribute or a textual node.+newtype Content content+ = -- | Parser in the context of an xml namespace URI by alias lookup function.+ Content ((Text -> Maybe Text) -> Text -> Either (Maybe ContentError) content)+ deriving+ (Functor, Applicative, Monad, Alternative, MonadPlus)+ via (ReaderT (Text -> Maybe Text) (ExceptT (Last ContentError) ((->) Text)))++instance MonadFail Content where+ fail = fromString >>> UserContentError >>> Just >>> Left >>> const >>> const >>> Content++-- |+-- Return the content as it is.+textContent :: Content Text+textContent =+ Content (const pure)++-- |+-- Map the content to a type if it's valid.+narrowedContent :: (Text -> Maybe a) -> Content a+narrowedContent mapper =+ Content (const (\x -> maybe (Left (Just (UnexpectedValueContentError x))) Right (mapper x)))++-- |+-- Parse the content with a possibly failing function.+refinedContent :: (Text -> Either Text a) -> Content a+refinedContent refine =+ Content (const (first (Just . ParsingContentError) . refine))++-- |+-- Map the content using a dictionary.+enumContent :: [(Text, a)] -> Content a+enumContent mappingList =+ let !expectedKeysList =+ fmap fst mappingList+ mappingListLength =+ length mappingList+ !narrow =+ if mappingListLength > 512+ then+ let !hashMap = HashMap.fromList mappingList+ in flip HashMap.lookup hashMap+ else flip List.lookup mappingList+ extract a =+ case narrow a of+ Just b -> Right b+ _ -> Left (Just (EnumContentError expectedKeysList a))+ in Content (const extract)++-- |+-- Parse the content using the \"attoparsec\" parser.+attoparsedContent :: Attoparsec.Parser a -> Content a+attoparsedContent parser =+ Content (const (first (Just . ParsingContentError . fromString) . Attoparsec.parseOnly parser))++-- |+-- Parse the content as XML Schema QName,+-- automatically resolving the namespace as URI and failing,+-- if none is associated.+--+-- Produces a URI associated with the namespace and name.+-- If the content does not contain colon, produces an unnamespaced name.+--+-- Refs:+--+-- - https://www.w3.org/2001/tag/doc/qnameids.html#sec-qnames-xml+-- - https://en.wikipedia.org/wiki/QName+qNameContent :: Content (Maybe Text, Text)+qNameContent =+ Content $ \lookup content -> case Attoparsec.parseOnly XmlSchemaAttoparsec.qName content of+ Right (ns, name) -> case ns of+ Just ns -> case lookup ns of+ Just uri -> Right (Just uri, name)+ Nothing -> Left (Just (NamespaceNotFoundContentError ns))+ Nothing -> Right (Nothing, name)+ Left err -> Left (Just (ParsingContentError (fromString err)))++-- * ByName++data ByNameResult deeperError content a+ = NotFoundByNameResult [(Maybe Text, Text)]+ | FailedDeeperByNameResult (Maybe Text) Text deeperError+ | OkByNameResult (NameMap.NameMap content) a+ deriving (Functor)++-- |+-- Composable extension to a parser, which looks up its input by name.+--+-- Useful for searching elements and attributes by name.+--+-- Alternative and MonadPlus alternate only on lookup errors.+-- When lookup is successful, but the deeper parser fails,+-- the error propagates.+--+-- Monad and Applicative sequentially fetch contents by matching names.+newtype ByName parser a+ = ByName+ ( forall content deeperError.+ NameMap.NameMap content ->+ (content -> forall x. parser x -> Either deeperError x) ->+ ByNameResult deeperError content a+ )++instance Functor (ByName parser) where+ fmap fn (ByName run) =+ ByName $ \map parse -> fmap fn (run map parse)++instance Applicative (ByName parser) where+ pure x =+ ByName $ \map _ -> OkByNameResult map x+ ByName runL <*> ByName runR =+ ByName $ \map parse -> case runL map parse of+ OkByNameResult map lRes -> runR map parse & fmap lRes+ NotFoundByNameResult unfoundNames -> NotFoundByNameResult unfoundNames+ FailedDeeperByNameResult ns name err -> FailedDeeperByNameResult ns name err++instance Monad (ByName parser) where+ return = pure+ ByName runL >>= k =+ ByName $ \map parse -> case runL map parse of+ OkByNameResult map lRes -> case k lRes of ByName runR -> runR map parse+ NotFoundByNameResult unfoundNames -> NotFoundByNameResult unfoundNames+ FailedDeeperByNameResult ns name err -> FailedDeeperByNameResult ns name err++instance Alternative (ByName parser) where+ empty =+ ByName $ \_ _ -> NotFoundByNameResult []+ ByName runL <|> ByName runR =+ ByName $ \map parse -> case runL map parse of+ OkByNameResult map lRes -> OkByNameResult map lRes+ NotFoundByNameResult unfoundNamesL -> case runR map parse of+ NotFoundByNameResult unfoundNamesR -> NotFoundByNameResult (unfoundNamesL <> unfoundNamesR)+ resR -> resR+ FailedDeeperByNameResult ns name err -> FailedDeeperByNameResult ns name err++instance MonadPlus (ByName parser) where+ mzero = empty+ mplus = (<|>)++-- |+-- Execute a parser on the result of looking up a content by namespace and name.+byName :: Maybe Text -> Text -> parser a -> ByName parser a+byName ns name parser =+ ByName $ \map parse ->+ case NameMap.fetch ns name map of+ Just (content, map) -> case parse content parser of+ Right a -> OkByNameResult map a+ Left err -> FailedDeeperByNameResult ns name err+ Nothing -> NotFoundByNameResult [(ns, name)]
+ library/XmlParser/ElementDestructionState.hs view
@@ -0,0 +1,59 @@+module XmlParser.ElementDestructionState+ ( ElementDestructionContext (..),+ ElementDestructionState,+ new,+ resolveAttributeNames,+ resolveChildNames,+ )+where++import qualified Text.XML as Xml+import qualified XmlParser.NameMap as NameMap+import qualified XmlParser.NamespaceRegistry as NamespaceRegistry+import XmlParser.Prelude++-- |+-- Context needed for the reading by functions of this component.+-- They however do not change these values.+--+-- You can use this as a parameter to a reader monad.+data ElementDestructionContext+ = ElementDestructionContext+ NamespaceRegistry.NamespaceRegistry+ -- ^ Namespace registry as seen from the context of this node.+ Xml.Element+ -- ^ The node that we're in the context of.++-- |+-- Used for the state of a parser in the context of specifically element node.+--+-- It is basically the state of the context of the parser.+--+-- You can use this as a parameter to the state monad.+data ElementDestructionState+ = ElementDestructionState+ (Maybe (NameMap.NameMap Text))+ -- ^ Cached attribute by name lookup map.+ (Maybe (NameMap.NameMap Xml.Element))+ -- ^ Cached child element by name lookup map.++new :: ElementDestructionState+new = ElementDestructionState Nothing Nothing++-- |+-- Cache attribute names once and return them.+resolveAttributeNames :: ElementDestructionContext -> ElementDestructionState -> (NameMap.NameMap Text, ElementDestructionState)+resolveAttributeNames+ (ElementDestructionContext nreg (Xml.Element _ attributes _))+ (ElementDestructionState attributeByNameMap childByNameMap) =+ let resolvedAttributeByNameMap = fromMaybe (NameMap.fromAttributes nreg attributes) attributeByNameMap+ in (resolvedAttributeByNameMap, ElementDestructionState (Just resolvedAttributeByNameMap) childByNameMap)++-- |+-- Cache child names once and return them.+resolveChildNames :: ElementDestructionContext -> ElementDestructionState -> (NameMap.NameMap Xml.Element, ElementDestructionState)+resolveChildNames+ (ElementDestructionContext nreg (Xml.Element _ _ nodes))+ (ElementDestructionState attributeByNameMap childByNameMap) =+ let resolvedChildByNameMap = fromMaybe (NameMap.fromNodes nreg nodes) childByNameMap+ in (resolvedChildByNameMap, ElementDestructionState attributeByNameMap (Just resolvedChildByNameMap))
+ library/XmlParser/NameMap.hs view
@@ -0,0 +1,106 @@+module XmlParser.NameMap+ ( NameMap,+ fromNodes,+ fromAttributes,+ empty,+ insert,+ fetch,+ extractNames,+ )+where++import qualified Data.HashMap.Strict as HashMap+import qualified Data.Map.Strict as Map+import qualified Text.XML as Xml+import qualified XmlParser.NamespaceRegistry as NamespaceRegistry+import XmlParser.Prelude hiding (empty, fromList, insert, toList)+import qualified XmlParser.TupleHashMap as TupleHashMap++data NameMap a+ = NameMap+ (TupleHashMap.TupleHashMap Text Text [a])+ -- ^ Namespaced+ (HashMap Text [a])+ -- ^ Unnamespaced++fromNodes :: NamespaceRegistry.NamespaceRegistry -> [Xml.Node] -> NameMap Xml.Element+fromNodes nreg =+ fromReverseList (flip NamespaceRegistry.resolveElementName nreg) . foldl' appendIfElement []+ where+ appendIfElement list = \case+ Xml.NodeElement element -> (Xml.elementName element, element) : list+ _ -> list++fromAttributes :: NamespaceRegistry.NamespaceRegistry -> Map Xml.Name Text -> NameMap Text+fromAttributes nreg =+ fromList (flip NamespaceRegistry.resolveAttributeName nreg) . Map.toList++fromList :: (Xml.Name -> Maybe (Maybe Text, Text)) -> [(Xml.Name, a)] -> NameMap a+fromList resolve =+ fromReverseList resolve . reverse++fromReverseList :: (Xml.Name -> Maybe (Maybe Text, Text)) -> [(Xml.Name, a)] -> NameMap a+fromReverseList resolve list =+ foldr step NameMap list TupleHashMap.empty HashMap.empty+ where+ step (name, contents) next !map1 !map2 =+ case resolve name of+ Nothing -> next map1 map2+ Just (ns, name) ->+ case ns of+ Just ns -> next (TupleHashMap.insertSemigroup ns name [contents] map1) map2+ Nothing -> next map1 (HashMap.insertWith (++) name [contents] map2)++empty :: NameMap a+empty =+ NameMap TupleHashMap.empty HashMap.empty++insert :: Maybe Text -> Text -> a -> NameMap a -> NameMap a+insert ns name contents (NameMap map1 map2) =+ case ns of+ Just ns ->+ NameMap (TupleHashMap.insertSemigroup ns name [contents] map1) map2+ Nothing ->+ NameMap map1 (HashMap.insertWith (++) name [contents] map2)++fetch :: Maybe Text -> Text -> NameMap a -> Maybe (a, NameMap a)+fetch ns name (NameMap map1 map2) =+ case ns of+ Just ns ->+ TupleHashMap.alterF+ ( \case+ Just list ->+ case list of+ head : tail -> case tail of+ [] -> Compose (Just (head, Nothing))+ _ -> Compose (Just (head, Just tail))+ _ -> Compose Nothing+ Nothing ->+ Compose Nothing+ )+ ns+ name+ map1+ & getCompose+ & fmap (fmap (flip NameMap map2))+ Nothing ->+ HashMap.alterF+ ( \case+ Just list ->+ case list of+ head : tail -> case tail of+ [] -> Compose (Just (head, Nothing))+ _ -> Compose (Just (head, Just tail))+ _ -> Compose Nothing+ Nothing ->+ Compose Nothing+ )+ name+ map2+ & getCompose+ & fmap (fmap (NameMap map1))++extractNames :: NameMap a -> [(Maybe Text, Text)]+extractNames (NameMap map1 map2) =+ fmap (\(a, b, _) -> (Just a, b)) (TupleHashMap.toList map1)+ <> fmap ((Nothing,) . fst) (HashMap.toList map2)
+ library/XmlParser/NamespaceRegistry.hs view
@@ -0,0 +1,82 @@+module XmlParser.NamespaceRegistry+ ( NamespaceRegistry,+ new,+ lookup,+ resolveElementName,+ resolveAttributeName,+ interpretAttribute,+ interpretAttributes,+ )+where++import qualified Data.Attoparsec.Text as Attoparsec+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Map.Strict as Map+import qualified Text.XML as Xml+import XmlParser.Prelude hiding (insert, lookup)+import qualified XmlParser.XmlSchemaAttoparsec as XmlSchemaAttoparsec++data NamespaceRegistry+ = NamespaceRegistry+ (HashMap Text Text)+ (Maybe Text)++new :: NamespaceRegistry+new = NamespaceRegistry HashMap.empty Nothing++lookup :: Text -> NamespaceRegistry -> Maybe Text+lookup ns (NamespaceRegistry nsMap _) =+ HashMap.lookup ns nsMap++resolveElementName :: Xml.Name -> NamespaceRegistry -> Maybe (Maybe Text, Text)+resolveElementName = resolveName True++resolveAttributeName :: Xml.Name -> NamespaceRegistry -> Maybe (Maybe Text, Text)+resolveAttributeName = resolveName False++resolveName :: Bool -> Xml.Name -> NamespaceRegistry -> Maybe (Maybe Text, Text)+resolveName useDef (Xml.Name localName uri ns) (NamespaceRegistry map def) =+ case uri of+ Just uri -> Just (Just uri, localName)+ Nothing -> case ns of+ Just ns -> case HashMap.lookup ns map of+ Just uri -> Just (Just uri, localName)+ Nothing -> Nothing+ Nothing ->+ case Attoparsec.parseOnly XmlSchemaAttoparsec.qName localName of+ Right (ns, localName) -> case ns of+ Just ns -> case HashMap.lookup ns map of+ Just uri -> Just (Just uri, localName)+ Nothing -> Nothing+ Nothing ->+ Just (if useDef then def else Nothing, localName)+ _ -> Nothing++insert :: Text -> Text -> NamespaceRegistry -> NamespaceRegistry+insert alias uri (NamespaceRegistry map def) =+ NamespaceRegistry (HashMap.insert alias uri map) def++setDefault :: Text -> NamespaceRegistry -> NamespaceRegistry+setDefault uri (NamespaceRegistry map _) =+ NamespaceRegistry map (Just uri)++-- |+-- Extend the registry by reading in the value if this is an \"xmlns\" attribute.+interpretAttribute :: Xml.Name -> Text -> NamespaceRegistry -> NamespaceRegistry+interpretAttribute (Xml.Name localName namespace prefix) uri =+ case namespace of+ Nothing -> case prefix of+ Nothing -> case Attoparsec.parseOnly XmlSchemaAttoparsec.qName localName of+ Right (Just "xmlns", name) -> insert name uri+ Right (Nothing, "xmlns") -> setDefault uri+ _ -> id+ _ -> id+ _ -> id++-- |+-- Extend the registry by reading in the \"xmlns\" attributes of an element.+--+-- Useful when diving into an element+interpretAttributes :: Map Xml.Name Text -> NamespaceRegistry -> NamespaceRegistry+interpretAttributes attributes x =+ Map.foldlWithKey' (\x name value -> interpretAttribute name value x) x attributes
+ library/XmlParser/NodeConsumerState.hs view
@@ -0,0 +1,45 @@+module XmlParser.NodeConsumerState+ ( NodeConsumerState,+ new,+ bumpOffset,+ getOffset,+ fetchNode,+ lookupNamespace,+ getNamespaceRegistry,+ )+where++import qualified Text.XML as Xml+import qualified XmlParser.NamespaceRegistry as NamespaceRegistry+import XmlParser.Prelude++data NodeConsumerState+ = NodeConsumerState+ [Xml.Node]+ -- ^ Nodes.+ Int+ -- ^ Offset.+ NamespaceRegistry.NamespaceRegistry+ -- ^ Namespace registry.++new :: [Xml.Node] -> NamespaceRegistry.NamespaceRegistry -> NodeConsumerState+new nodes nsReg = NodeConsumerState nodes 0 nsReg++bumpOffset :: NodeConsumerState -> NodeConsumerState+bumpOffset (NodeConsumerState a b c) = NodeConsumerState a (succ b) c++getOffset :: NodeConsumerState -> Int+getOffset (NodeConsumerState _ x _) = x++fetchNode :: NodeConsumerState -> Maybe (Xml.Node, NodeConsumerState)+fetchNode (NodeConsumerState nodes offset nsReg) =+ case nodes of+ nodesHead : nodesTail -> Just (nodesHead, NodeConsumerState nodesTail offset nsReg)+ _ -> Nothing++lookupNamespace :: Text -> NodeConsumerState -> Maybe Text+lookupNamespace ns (NodeConsumerState _ _ nsReg) =+ NamespaceRegistry.lookup ns nsReg++getNamespaceRegistry :: NodeConsumerState -> NamespaceRegistry.NamespaceRegistry+getNamespaceRegistry (NodeConsumerState _ _ x) = x
+ library/XmlParser/Prelude.hs view
@@ -0,0 +1,90 @@+module XmlParser.Prelude+ ( module Exports,+ tryMapping,+ )+where++import Control.Applicative as Exports hiding (WrappedArrow (..))+import Control.Arrow as Exports hiding (first, second)+import Control.Category as Exports+import Control.Concurrent as Exports+import Control.Exception as Exports+import Control.Monad as Exports hiding (fail, forM, forM_, mapM, mapM_, msum, sequence, sequence_)+import Control.Monad.Fail as Exports+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.IO.Class as Exports+import Control.Monad.ST as Exports+import Control.Monad.Trans.Class as Exports+import Control.Monad.Trans.Cont as Exports hiding (callCC, shift)+import Control.Monad.Trans.Except as Exports (Except, ExceptT (ExceptT), catchE, except, mapExcept, mapExceptT, runExcept, runExceptT, throwE, withExcept, withExceptT)+import Control.Monad.Trans.Maybe as Exports+import Control.Monad.Trans.Reader as Exports (Reader, ReaderT (ReaderT), ask, mapReader, mapReaderT, runReader, runReaderT, withReader, withReaderT)+import Control.Monad.Trans.State.Strict as Exports (State, StateT (StateT), evalState, evalStateT, execState, execStateT, mapState, mapStateT, runState, runStateT, withState, withStateT)+import Control.Monad.Trans.Writer.Strict as Exports (Writer, WriterT (..), execWriter, execWriterT, mapWriter, mapWriterT, runWriter)+import Data.Bifunctor as Exports+import Data.Bits as Exports+import Data.Bool as Exports+import Data.ByteString as Exports (ByteString)+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports hiding (toList)+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports+import Data.Functor.Compose as Exports+import Data.Functor.Identity as Exports+import Data.HashMap.Strict as Exports (HashMap)+import Data.Hashable as Exports (Hashable)+import Data.IORef as Exports+import Data.Int as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons)+import Data.List.NonEmpty as Exports (NonEmpty (..))+import Data.Map.Strict as Exports (Map)+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (Alt)+import Data.Ord as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports+import Data.Semigroup as Exports hiding (First (..), Last (..))+import Data.String as Exports+import Data.Text as Exports (Text)+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.Unique as Exports+import Data.Version as Exports+import Data.Void as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import Foreign.ForeignPtr as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports+import GHC.Conc as Exports hiding (orElse, threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)+import GHC.Exts as Exports (IsList (..), groupWith, inline, lazy, sortWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import GHC.OverloadedLabels as Exports+import Numeric as Exports+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports (Handle, hClose)+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.ParserCombinators.ReadP as Exports (ReadP, readP_to_S, readS_to_P)+import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readP_to_Prec, readPrec_to_P, readPrec_to_S, readS_to_Prec)+import Text.Printf as Exports (hPrintf, printf)+import Text.Read as Exports (Read (..), readEither, readMaybe)+import Unsafe.Coerce as Exports+import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, fail, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.))++tryMapping :: Exception e => (e -> e') -> IO a -> IO (Either e' a)+tryMapping f action = catch (fmap Right action) (pure . Left . f)
+ library/XmlParser/TupleHashMap.hs view
@@ -0,0 +1,55 @@+module XmlParser.TupleHashMap+ ( TupleHashMap,+ KeyConstraints,+ empty,+ insertSemigroup,+ alterF,+ toList,+ )+where++import qualified Data.HashMap.Strict as HashMap+import XmlParser.Prelude hiding (empty, fromList, toList)++newtype TupleHashMap k1 k2 v = TupleHashMap (HashMap k1 (HashMap k2 v))++-- |+-- Serves to reduce noise in signatures.+type KeyConstraints k1 k2 = (Eq k1, Hashable k1, Eq k2, Hashable k2)++empty :: TupleHashMap k1 k2 v+empty =+ TupleHashMap HashMap.empty++insertSemigroup :: (Semigroup v, KeyConstraints k1 k2) => k1 -> k2 -> v -> TupleHashMap k1 k2 v -> TupleHashMap k1 k2 v+insertSemigroup k1 k2 v (TupleHashMap map1) =+ HashMap.alter+ ( maybe+ (Just (HashMap.singleton k2 v))+ (Just . HashMap.insertWith (<>) k2 v)+ )+ k1+ map1+ & TupleHashMap++alterF :: (Functor f, KeyConstraints k1 k2) => (Maybe v -> f (Maybe v)) -> k1 -> k2 -> TupleHashMap k1 k2 v -> f (TupleHashMap k1 k2 v)+alterF fn k1 k2 (TupleHashMap map1) =+ HashMap.alterF+ ( \case+ Just map2 ->+ HashMap.alterF fn k2 map2+ & fmap (\map2 -> if HashMap.null map2 then Nothing else Just map2)+ Nothing ->+ fn Nothing+ & (fmap . fmap) (HashMap.singleton k2)+ )+ k1+ map1+ & fmap TupleHashMap++toList :: TupleHashMap k1 k2 b -> [(k1, k2, b)]+toList (TupleHashMap map1) =+ do+ (k1, map2) <- HashMap.toList map1+ (k2, v) <- HashMap.toList map2+ return (k1, k2, v)
+ library/XmlParser/XmlConduitWrapper.hs view
@@ -0,0 +1,44 @@+-- |+-- A minimal wrapper over xml-conduit parsing API bringing it to our standards.+module XmlParser.XmlConduitWrapper+ ( parseByteString,+ parseLazyByteString,+ parseFile,+ )+where++import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Text as Text+import qualified Text.XML as XmlConduit+import qualified Text.XML.Unresolved as XmlConduit (InvalidEventStream (..))+import XmlParser.Prelude++parseByteString :: ByteString -> Either Text XmlConduit.Document+parseByteString =+ parseLazyByteString . LazyByteString.fromStrict++parseLazyByteString :: LazyByteString.ByteString -> Either Text XmlConduit.Document+parseLazyByteString input =+ first renderError (XmlConduit.parseLBS settings input)++parseFile :: FilePath -> IO (Either Text XmlConduit.Document)+parseFile path =+ tryMapping renderError (XmlConduit.readFile settings path)++settings :: XmlConduit.ParseSettings+settings =+ XmlConduit.def+ { XmlConduit.psRetainNamespaces = True+ }++renderError :: SomeException -> Text+renderError e+ | Just e <- fromException @XmlConduit.XMLException e =+ fromString (show e)+ | Just e <- fromException @XmlConduit.InvalidEventStream e =+ fromString (show e)+ | Just (XmlConduit.UnresolvedEntityException e) <- fromException @XmlConduit.UnresolvedEntityException e =+ "Unresolved entities: " <> Text.intercalate "," (toList e)+ | otherwise =+ -- FIXME: Find other cases and do something more user-friendly about them+ fromString (show e)
+ library/XmlParser/XmlSchemaAttoparsec.hs view
@@ -0,0 +1,74 @@+module XmlParser.XmlSchemaAttoparsec where++import Data.Attoparsec.Text+import qualified Data.Text as Text+import XmlParser.Prelude hiding (takeWhile)++qName :: Parser (Maybe Text, Text)+qName =+ {-+ Ref (from https://en.wikipedia.org/wiki/QName):++ QName ::= PrefixedName | UnprefixedName+ PrefixedName ::= Prefix ':' LocalPart+ UnprefixedName ::= LocalPart+ Prefix ::= NCName+ LocalPart ::= NCName+ -}+ do+ a <- ncName+ asum+ [ do+ char ':'+ b <- ncName+ return (Just a, b),+ return (Nothing, a)+ ]++{-# NOINLINE ncName #-}+ncName :: Parser Text+ncName =+ {-+ Ref (from https://en.wikipedia.org/wiki/QName):++ NCName ::= Name - (Char* ':' Char*) (* An XML Name, minus the ":" *)+ Name ::= NameStartChar (NameChar)*+ NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]+ | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF]+ | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF]+ | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD]+ | [#x10000-#xEFFFF]+ NameChar ::= NameStartChar | "-" | "." | [0-9]+ | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]+ Char ::= (* any Unicode char, excluding surrogate blocks FFFE and FFFF. *)+ #x9 | #xA | #xD | [#x20-#xD7FF]+ | [#xE000-#xFFFD] | [#x10000-#x10FFFF]+ -}+ do+ a <- satisfy nameStartCharPredicate+ b <- takeWhile nameCharPredicate+ return (Text.cons a b)+ where+ nameStartCharPredicate x =+ x >= 'A' && x <= 'Z'+ || x == '_'+ || x >= 'a' && x <= 'z'+ || x >= '\xC0' && x <= '\xD6'+ || x >= '\xD8' && x <= '\xF6'+ || x >= '\xF8' && x <= '\x2FF'+ || x >= '\x370' && x <= '\x37D'+ || x >= '\x37F' && x <= '\x1FFF'+ || x >= '\x200C' && x <= '\x200D'+ || x >= '\x2070' && x <= '\x218F'+ || x >= '\x2C00' && x <= '\x2FEF'+ || x >= '\x3001' && x <= '\xD7FF'+ || x >= '\xF900' && x <= '\xFDCF'+ || x >= '\xFDF0' && x <= '\xFFFD'+ || x >= '\x10000' && x <= '\xEFFFF'+ nameCharPredicate x =+ x == '-' || x == '.'+ || x >= '0' && x <= '9'+ || x == '\xB7'+ || x >= '\x0300' && x <= '\x036F'+ || x >= '\x203F' && x <= '\x2040'+ || nameStartCharPredicate x
+ test/Main.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE OverloadedLists #-}++module Main where++import qualified Data.ByteString.Lazy as LazyByteString+import Test.QuickCheck.Instances ()+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import qualified Text.XML as Xc+import qualified XmlParser as Xp+import Prelude hiding (assert)++main =+ defaultMain $+ testGroup "All tests" $+ [ testProperty "ByName/many" $ do+ bContents <- fmap (fromString . show) <$> listOf (chooseInt (0, 99))++ xml <-+ let childByName name =+ Xc.Element name [] . pure . Xc.NodeContent+ root =+ Xc.Element "a" [] . fmap (Xc.NodeElement)+ in fmap (documentByteString . elementDocument . root . join) $+ forM bContents $ \bContent -> do+ prefix <-+ oneof+ [ pure [],+ listOf (childByName "c" <$> arbitrary)+ ]+ return (prefix <> [childByName "b" bContent])++ let parser =+ Xp.childrenByName $ many $ Xp.byName Nothing "b" $ Xp.children $ Xp.contentNode $ Xp.textContent+ result = Xp.parseByteString parser xml++ return (result === Right bContents),+ testCase "Namespaces" $ do+ let parser = Xp.childrenByName $ Xp.byName (Just "B") "c" $ Xp.children $ Xp.contentNode $ Xp.textContent+ result = Xp.parseByteString parser "<a xmlns:b=\"B\"><b:c>d</b:c></a>"+ assertEqual "" (Right "d") result,+ testCase "Error" $ do+ let parser = Xp.childrenByName $ Xp.byName Nothing "c" $ Xp.children $ Xp.contentNode $ Xp.textContent+ result = Xp.parseByteString parser "<a><b>c</b><d/></a>"+ assertEqual "" (Left "/: None of following child element names found: [c]. Names available: [b, d]") result,+ testCase "QName content" $ do+ let input = "<root xmlns:abc='abc-uri'>abc:d</root>"+ parser = Xp.children $ Xp.contentNode $ Xp.qNameContent+ in assertEqual "" (Right (Just "abc-uri", "d")) (Xp.parseByteString parser input)+ ]++documentByteString :: Xc.Document -> ByteString+documentByteString = LazyByteString.toStrict . Xc.renderLBS Xc.def++elementDocument :: Xc.Element -> Xc.Document+elementDocument x =+ Xc.Document (Xc.Prologue [] Nothing []) x []
+ xml-parser.cabal view
@@ -0,0 +1,64 @@+cabal-version: 3.0+name: xml-parser+version: 0.1+synopsis: XML parser with informative error-reporting and simple API+homepage: https://github.com/nikita-volkov/xml-parser+bug-reports: https://github.com/nikita-volkov/xml-parser/issues+author: Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>+copyright: (c) 2021 Nikita Volkov+license: MIT+license-file: LICENSE+build-type: Simple++source-repository head+ type: git+ location: git://github.com/nikita-volkov/xml-parser.git++library+ hs-source-dirs: library+ default-extensions: BangPatterns, BinaryLiterals, BlockArguments, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, DerivingVia, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedSums, UnboxedTuples, ViewPatterns+ default-language: Haskell2010+ ghc-options:+ -funbox-strict-fields+ exposed-modules:+ XmlParser+ other-modules:+ XmlParser.AstParser+ XmlParser.ElementDestructionState+ XmlParser.NameMap+ XmlParser.NamespaceRegistry+ XmlParser.NodeConsumerState+ XmlParser.TupleHashMap+ XmlParser.Prelude+ XmlParser.XmlConduitWrapper+ XmlParser.XmlSchemaAttoparsec+ build-depends:+ attoparsec >=0.13 && <0.15,+ base >=4.11 && <5,+ bytestring >=0.10 && <0.12,+ containers ^>=0.6,+ hashable >=1.2 && <2,+ text >=1.2.4 && <2,+ text-builder >=0.6.6.2 && <0.7,+ transformers ^>=0.5,+ unordered-containers ^>=0.2.14,+ xml-conduit ^>=1.9.1.1,++test-suite test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ default-extensions: BangPatterns, BinaryLiterals, BlockArguments, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, DerivingVia, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators, UnboxedSums, UnboxedTuples, ViewPatterns+ default-language: Haskell2010+ main-is: Main.hs+ other-modules:+ build-depends:+ attoparsec >=0.13 && <0.15,+ QuickCheck >=2.8.1 && <3,+ quickcheck-instances >=0.3.11 && <0.4,+ rerebase >=1.6.1 && <2,+ tasty >=0.12 && <2,+ tasty-hunit >=0.9 && <0.11,+ tasty-quickcheck >=0.9 && <0.11,+ xml-conduit,+ xml-parser,