packages feed

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 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,