From 7cd6a0819c63028aaad1c3d1368ff0e31d1ac35a Mon Sep 17 00:00:00 2001 From: Alice BRENON <alice.brenon@ens-lyon.fr> Date: Mon, 15 Feb 2021 18:13:08 +0100 Subject: [PATCH] Switch error handling to MonadError instead of Either and establish a clearer typology of errors --- lib/Data/PositionTree.hs | 44 +++++++++++++++----------------- lib/Text/InvisiXML.hs | 38 +++++++++++---------------- lib/Text/InvisiXML/Error.hs | 41 ++++++++++++++++++++++++++--- lib/Text/InvisiXML/Namespace.hs | 5 ---- lib/Text/XML/Light/Serializer.hs | 40 +++++++++++++++++++---------- 5 files changed, 99 insertions(+), 69 deletions(-) diff --git a/lib/Data/PositionTree.hs b/lib/Data/PositionTree.hs index 6838dd5..c411f1c 100644 --- a/lib/Data/PositionTree.hs +++ b/lib/Data/PositionTree.hs @@ -12,17 +12,18 @@ module Data.PositionTree ( ) where import Control.Monad (foldM) -import Control.Monad.Except (MonadError) +import Control.Monad.Except (MonadError(..)) import Data.Map as Map (Map, alter, foldrWithKey) import qualified Data.Map as Map (empty) import qualified Data.Map.Merge.Strict as Map (merge, preserveMissing, zipWithMatched) import Data.Text as Text (Text, length) -import Text.XML.Light (Content(..), Element(..), ppContent, showQName) -import Text.XML.Light.Serializer (FromXML(..), ToXML(..), (.=), expected) -import Text.InvisiXML.Error (StructureError) -import Text.InvisiXML.Namespace (addAttr, getAttr, ixml, setChildren) -import Text.Printf (printf) -import Text.Read (readEither, readPrec) +import Text.XML.Light (Content(..), Element(..)) +import Text.XML.Light.Serializer ( + FromXML(..), ToXML(..), (.=), expectElement, readAttr + ) +import Text.InvisiXML.Error (ParsingError(..), StructureError) +import Text.InvisiXML.Namespace (addAttr, ixml, setChildren) +import Text.Read (readPrec) newtype Position = Position { getPosition :: Int @@ -50,14 +51,15 @@ data Node a = deriving (Show) instance FromXML a => FromXML (Node a) where - fromXML c@[Elem e] = - case getAttr (ixml "to") e of - Just to -> Range - <$> readEither to - <*> fromXML c - <*> fromXML (elContent e) - _ -> Point <$> fromXML c - fromXML c = expected "Node" c + fromXML = expectElement "Node" $ \e@(Element {elContent}) -> + let value = fromXML [Elem e] in + (Range <$> (readAttr "Position" qTo e) <*> value <*> fromXML elContent) + `catchError` (pointIfNoTo value) + where + qTo = ixml "to" + pointIfNoTo value e@(MissingAttribute {}) + | missingQName e == qTo = Point <$> value + pointIfNoTo _ e = throwError e instance ToXML a => ToXML (Node a) where toXML (Point p) = toXML p @@ -69,14 +71,10 @@ newtype PositionTree a = PositionTree (Map Position [Node a]) deriving (Show) instance FromXML a => FromXML (PositionTree a) where fromXML contents = foldM addNode empty [e | (Elem e) <- contents] where - qAt = ixml "at" - addNode positionTree e = - let c = Elem e in - case getAttr qAt e of - Just at -> - addSibling <$> readEither at <*> fromXML [c] <*> return positionTree - Nothing -> Left $ printf - "Missing %s attribute on node %s" (showQName qAt) (ppContent c) + addNode positionTree e = addSibling + <$> readAttr "Position" (ixml "at") e + <*> fromXML [Elem e] + <*> return positionTree instance ToXML a => ToXML (PositionTree a) where toXML (PositionTree m) = foldrWithKey nodesToXML [] m diff --git a/lib/Text/InvisiXML.hs b/lib/Text/InvisiXML.hs index 3372451..4f6a418 100644 --- a/lib/Text/InvisiXML.hs +++ b/lib/Text/InvisiXML.hs @@ -18,14 +18,15 @@ import Data.PositionTree as PositionTree ( import qualified Data.PositionTree as PositionTree (merge) import Data.Text (Text) import qualified Data.Text as Text (concat, null, pack) -import Text.InvisiXML.Error (Occurrence(..), StructureError(..), XMLError(..)) +import Text.InvisiXML.Error ( + ParsingError(..), TagOccurrence(..), StructureError(..), XMLError(..) + ) import Text.InvisiXML.Namespace (prefix, uRI, ixml) import Text.XML.Light ( - Attr(..), Content(..), Element(..), Line, QName(..), node, showCData - , showContent + Attr(..), Content(..), Element(..), QName(..), node, showCData, showContent ) import Text.XML.Light.Lexer (Token(..), XmlSource, tokens) -import Text.XML.Light.Serializer (FromXML(..), ToXML(..), expected) +import Text.XML.Light.Serializer (FromXML(..), ToXML(..), expectElement) data FrozenElement = FrozenElement { frozenName :: QName @@ -33,11 +34,8 @@ data FrozenElement = FrozenElement { } deriving (Show) instance FromXML FrozenElement where - fromXML [Elem (Element {elName, elAttribs})] = Right $ FrozenElement { - frozenName = elName - , frozenAttrs = elAttribs - } - fromXML c = expected "FrozenElement" c + fromXML = expectElement "FrozenElement" $ \(Element {elName, elAttribs}) -> + return $ FrozenElement {frozenName = elName, frozenAttrs = elAttribs} instance ToXML FrozenElement where toXML (FrozenElement {frozenName, frozenAttrs}) = @@ -53,9 +51,11 @@ data Structure = Structure { } deriving (Show) instance FromXML Structure where - fromXML [Elem e] - | elName e == ixml "structure" = Structure <$> fromXML (elContent e) - fromXML c = expected "Structure" c + fromXML = expectElement "Structure" parseStructure + where + parseStructure e + | elName e == ixml "structure" = Structure <$> fromXML (elContent e) + | otherwise = throwError . UnexpectedContent "Structure" $ Elem e instance ToXML Structure where toXML (Structure s) = [Elem $ node (ixml "structure") ([Attr ns uRI], toXML s)] @@ -70,11 +70,6 @@ data InvisiXML = InvisiXML { , text :: Text } -data TagOccurrence = TagOccurrence { - tag :: QName - , openLine :: Line - } - type Context = Maybe TagOccurrence data ParsingState = ParsingState { @@ -142,13 +137,10 @@ restore (context, subStructure) = state $ \parsingState -> checkout :: MonadError XMLError m => Context -> Parser m () checkout actual = gets context >>= compareWith actual where - compareWith (Just (TagOccurrence {tag, openLine})) Nothing = - throwError . ClosingUnopen tag $ Line openLine - compareWith Nothing (Just (TagOccurrence {tag, openLine})) = - throwError . Unclosed tag $ Line openLine + compareWith (Just tO) Nothing = throwError $ ClosingUnopen tO + compareWith Nothing (Just tO) = throwError $ Unclosed tO compareWith (Just tO0) (Just tO1) - | tag tO0 /= tag tO1 = - throwError $ ClosingDoesntMatch (tag tO0) (tag tO1) (Line $ openLine tO0) + | tag tO0 /= tag tO1 = throwError $ Mismatch {open = tO0, close = tO1} compareWith _ _ = return () parse :: (XmlSource s, MonadError XMLError m) => s -> m InvisiXML diff --git a/lib/Text/InvisiXML/Error.hs b/lib/Text/InvisiXML/Error.hs index 4420fa4..9bb3e9e 100644 --- a/lib/Text/InvisiXML/Error.hs +++ b/lib/Text/InvisiXML/Error.hs @@ -1,21 +1,53 @@ module Text.InvisiXML.Error ( InvisiXMLError(..) , Occurrence(..) + , ParsingError(..) , StructureError(..) + , TagOccurrence(..) , XMLError(..) ) where -import Text.XML.Light (Line, QName) +import Text.XML.Light (Content, Line, QName) data Occurrence = Line Line | EOF deriving Show +data TagOccurrence = TagOccurrence { + tag :: QName + , openLine :: Line + } + deriving Show + data XMLError = - ClosingUnopen QName Occurrence - | ClosingDoesntMatch QName QName Occurrence - | Unclosed QName Occurrence + ClosingUnopen TagOccurrence + | Mismatch { + open :: TagOccurrence + , close :: TagOccurrence + } + | Unclosed TagOccurrence + deriving Show + +data ParsingError = + UnexpectedContent { + expectedData :: String + , found :: Content + } + | BadAttribute { + expectedData :: String + , badQName :: QName + , expectedInfo :: String + } + | MissingAttribute { + expectedData :: String + , missingQName :: QName + } + | WrongNumber { + expectedData :: String + , expectedNumber :: Int + , actualNumber :: Int + } deriving Show data StructureError = @@ -25,5 +57,6 @@ data StructureError = data InvisiXMLError = XMLError XMLError + | ParsingError ParsingError | StructureError StructureError deriving Show diff --git a/lib/Text/InvisiXML/Namespace.hs b/lib/Text/InvisiXML/Namespace.hs index 9449141..6dc0e58 100644 --- a/lib/Text/InvisiXML/Namespace.hs +++ b/lib/Text/InvisiXML/Namespace.hs @@ -4,11 +4,9 @@ module Text.InvisiXML.Namespace ( , prefix , ixml , addAttr - , getAttr , setChildren ) where -import Data.List (find) import Text.XML.Light (Attr(..), Content(..), Element(..), QName(..), add_attr) uRI :: String @@ -27,8 +25,5 @@ onContent _ x = x addAttr :: Attr -> Content -> Content addAttr = onContent . add_attr -getAttr :: QName -> Element -> Maybe String -getAttr key = fmap attrVal . find ((==) key . attrKey) . elAttribs - setChildren :: [Content] -> Content -> Content setChildren elContent = onContent $ \e -> e {elContent} diff --git a/lib/Text/XML/Light/Serializer.hs b/lib/Text/XML/Light/Serializer.hs index fd92aea..25b0a65 100644 --- a/lib/Text/XML/Light/Serializer.hs +++ b/lib/Text/XML/Light/Serializer.hs @@ -1,29 +1,31 @@ +{-# LANGUAGE FlexibleContexts #-} module Text.XML.Light.Serializer ( FromXML(..) , ToXML(..) , decode - , eitherDecode , encode , (.=) - , expected + , expectElement + , readAttr ) where -import Data.List (intercalate) -import Text.Printf (printf) -import Text.XML.Light (Attr(..), Content, QName, parseXML, ppContent) +import Control.Monad.Except (MonadError(..)) +import Data.List (find, intercalate) +import Text.Read (readEither) +import Text.XML.Light ( + Attr(..), Content(..), Element(..), QName, parseXML, ppContent + ) import Text.XML.Light.Lexer (XmlSource) +import Text.InvisiXML.Error (ParsingError(..)) class FromXML a where - fromXML :: [Content] -> Either String a + fromXML :: MonadError ParsingError m => [Content] -> m a class ToXML a where toXML :: a -> [Content] -decode :: (XmlSource s, FromXML a) => s -> Maybe a -decode = either (\_ -> Nothing) Just . eitherDecode - -eitherDecode :: (XmlSource s, FromXML a) => s -> Either String a -eitherDecode = fromXML . parseXML +decode :: (XmlSource s, FromXML a, MonadError ParsingError m) => s -> m a +decode = fromXML . parseXML encode :: ToXML a => a -> String encode = intercalate "\n" . fmap ppContent . toXML @@ -31,6 +33,16 @@ encode = intercalate "\n" . fmap ppContent . toXML (.=) :: Show v => QName -> v -> Attr k .= v = Attr k $ show v -expected :: String -> [Content] -> Either String a -expected elemType = - Left . printf "Expected %s but got %s" elemType . concat . fmap ppContent +expectElement :: MonadError ParsingError m => + String -> (Element -> m a) -> [Content] -> m a +expectElement _ parser [Elem e] = parser e +expectElement dataName _ [c] = throwError $ UnexpectedContent dataName c +expectElement dataName _ l = throwError . WrongNumber dataName 1 $ length l + +readAttr :: (MonadError ParsingError m, Read a) => String -> QName -> Element -> m a +readAttr dataName key = + maybe (throwError $ MissingAttribute dataName key) + (either (throwError . BadAttribute dataName key) + return + . readEither . attrVal) + . find ((==) key . attrKey) . elAttribs -- GitLab