data-named 0.5.2 → 0.6.1
raw patch · 10 files changed
+610/−575 lines, 10 filesdep ~attoparsecdep ~basedep ~binaryPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: attoparsec, base, binary, containers, text
API changes (from Hackage documentation)
- Data.Named.Graph: bounds :: Graph n w -> (w, w)
- Data.Named.Graph: edgeMap :: Graph n w -> Map n [Either n w]
- Data.Named.Graph: instance Monad (RanM w)
- Data.Named.IOB: instance (Show w, Show a) => Show (IOB w a)
- Data.Named.IOB: instance Binary a => Binary (Atom a)
- Data.Named.IOB: instance Eq a => Eq (Atom a)
- Data.Named.IOB: instance Functor Atom
- Data.Named.IOB: instance Ord a => Ord (Atom a)
- Data.Named.IOB: instance Show a => Show (Atom a)
- Data.Named.IOB: label :: IOB w a -> Label a
- Data.Named.IOB: word :: IOB w a -> w
- Data.Named.Tree: beg :: Span w -> w
- Data.Named.Tree: end :: Span w -> w
- Data.Named.Tree: instance Eq w => Eq (Span w)
- Data.Named.Tree: instance Ord w => Ord (Span w)
- Data.Named.Tree: instance Show w => Show (Span w)
+ Data.Named.Graph: [bounds] :: Graph n w -> (w, w)
+ Data.Named.Graph: [edgeMap] :: Graph n w -> Map n [Either n w]
+ Data.Named.Graph: instance GHC.Base.Applicative (Data.Named.Graph.RanM w)
+ Data.Named.Graph: instance GHC.Base.Functor (Data.Named.Graph.RanM w)
+ Data.Named.Graph: instance GHC.Base.Monad (Data.Named.Graph.RanM w)
+ Data.Named.IOB: [label] :: IOB w a -> Label a
+ Data.Named.IOB: [word] :: IOB w a -> w
+ Data.Named.IOB: instance (GHC.Show.Show a, GHC.Show.Show w) => GHC.Show.Show (Data.Named.IOB.IOB w a)
+ Data.Named.IOB: instance Data.Binary.Class.Binary a => Data.Binary.Class.Binary (Data.Named.IOB.Atom a)
+ Data.Named.IOB: instance GHC.Base.Functor Data.Named.IOB.Atom
+ Data.Named.IOB: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Named.IOB.Atom a)
+ Data.Named.IOB: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Named.IOB.Atom a)
+ Data.Named.IOB: instance GHC.Show.Show a => GHC.Show.Show (Data.Named.IOB.Atom a)
+ Data.Named.Tree: [beg] :: Span w -> w
+ Data.Named.Tree: [end] :: Span w -> w
+ Data.Named.Tree: concatForestLeaves :: NeForest a [b] -> NeForest a b
+ Data.Named.Tree: concatTreeLeaves :: NeTree a [b] -> NeForest a b
+ Data.Named.Tree: instance GHC.Classes.Eq w => GHC.Classes.Eq (Data.Named.Tree.Span w)
+ Data.Named.Tree: instance GHC.Classes.Ord w => GHC.Classes.Ord (Data.Named.Tree.Span w)
+ Data.Named.Tree: instance GHC.Show.Show w => GHC.Show.Show (Data.Named.Tree.Span w)
Files
- Data/Named/Graph.hs +0/−113
- Data/Named/IOB.hs +0/−137
- Data/Named/Tree.hs +0/−155
- Text/Named/Enamex.hs +0/−164
- changelog +9/−0
- data-named.cabal +10/−6
- src/Data/Named/Graph.hs +124/−0
- src/Data/Named/IOB.hs +137/−0
- src/Data/Named/Tree.hs +166/−0
- src/Text/Named/Enamex.hs +164/−0
− Data/Named/Graph.hs
@@ -1,113 +0,0 @@-{-# LANGUAGE DoAndIfThenElse #-}---- | Implementation of a graph with each internal node identified by a--- unique key and each leaf represented by a position in the sentence.--module Data.Named.Graph-( Graph (..)-, mkGraph-, edges-, roots-, toForest-) where--import Prelude hiding (span)-import Data.Either (lefts, rights)-import Data.Ix (Ix, range, inRange)-import qualified Data.Set as S-import qualified Data.Map as M--import Data.Named.Tree---- | A graph over a sentence.-data Graph n w = Graph- { bounds :: (w, w)- , edgeMap :: M.Map n [Either n w] }---- | Make a graph given the bounds and list of edges.-mkGraph :: (Ord n, Ix w) => (w, w) -> [(n, [Either n w])] -> Graph n w-mkGraph bs =- Graph bs . M.fromList . map check- where- check (k, ks)- | null ks =- error "mkGraph: Left, internal node without output edges"- | any (not . inRange bs) (rights ks) =- error "mkGraph: Right, leaf node outside of bounds"- | otherwise = (k, ks)---- | Get keys of adjacent nodes for the given node key.-edges :: Ord n => Graph n w -> n -> [Either n w]-edges g k = case M.lookup k (edgeMap g) of- Nothing -> error "edges: key not in the map"- Just v -> v-{-# INLINE edges #-}---- | Return all graph roots (i.e. nodes with no parents).-roots :: Ord n => Graph n w -> [n]-roots g =- let desc = S.fromList . lefts . concat . M.elems $ edgeMap g- in [k | k <- M.keys (edgeMap g), not (k `S.member` desc)]--generate :: Ord n => Graph n w -> Either n w -> NeTree n w-generate g (Left k) = Node (Left k) (map (generate g) (edges g k))-generate _ w = Node w []--prune :: Ord w => NeForest n w -> NeForest n w-prune = unSpanForest . run . chop . sortForest . spanForest---- | Combine the disjoint forest with the list of words.--- Discontinuities will be patched with no trace.-addWords :: Ix w => (w, w) -> NeForest n w -> NeForest n w-addWords (p, q) [] = [Node (Right x) [] | x <- range (p, q)]-addWords (p, q) ts- = unSpanForest . subForest- . sortTree . fillTree- . dummyRoot- . spanForest $ ts- where- dummyRoot = Node (undefined, Span p q)- mkLeaf k = Node (Right k, leafSpan k) []-- fillForest = map fillTree- fillTree (Node n []) = Node n []- fillTree (Node (k, s) us) =- let m = spanSet s S.\\ S.unions (map (spanSet . span) us)- in Node (k, s) (fillForest us ++ map mkLeaf (S.toList m))---- | Transform a graph into a disjoint forest, i.e. a forest with--- no mutually overlapping trees.--- The process is lossy, discontinuity and overlapping cannot--- be represented with the `NeForest` data type.-toForest :: (Ord n, Ix w) => Graph n w -> NeForest n w-toForest g = addWords (bounds g) . prune . map (generate g . Left) . roots $ g---- | A stateful monad for forest pruning.-newtype RanM w a = RanM { runRanM :: Maybe w -> (a, Maybe w) }--instance Monad (RanM w) where- return x = RanM $ \s -> (x, s)- RanM v >>= f = RanM $ \s -> case v s of (x, s') -> runRanM (f x) s'--run :: RanM w a -> a-run act = fst (runRanM act Nothing)--contains :: Ord w => w -> RanM w Bool-contains k = RanM $ \m -> case m of- Just x -> (k <= x, m)- Nothing -> (False, m)--include :: w -> RanM w ()-include k = RanM $ \_ -> ((), Just k)--chop :: Ord w => Forest (k, Span w) -> RanM w (Forest (k, Span w))-chop [] = return []-chop (Node (k, s) ts : us) = do- visited <- contains (end s)- if visited then- chop us- else do- as <- chop ts- include (end s)- bs <- chop us- return (Node (k, s) as : bs)
− Data/Named/IOB.hs
@@ -1,137 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}--{- |- IOB encoding method extended to forests.-- Example:-->>> :m Data.Named.IOB Data.Named.Tree Text.Named.Enamex Data.Text.Lazy->>> let enamex = pack "<x>w1.1\\ w1.2</x> w2 <y><z>w3</z> w4</y>"-->>> putStr . drawForest . mapForest show . parseForest $ enamex-Left "x"-|-`- Right "w1.1 w1.2"-,-Right "w2"-,-Left "y"-|-+- Left "z"-| |-| `- Right "w3"-|-`- Right "w4"-->>> mapM_ print . encodeForest . parseForest $ enamex-IOB {word = "w1.1 w1.2", label = [B "x"]}-IOB {word = "w2", label = []}-IOB {word = "w3", label = [B "y",B "z"]}-IOB {word = "w4", label = [I "y"]}--}--module Data.Named.IOB-( IOB (..)-, Label-, Atom (..)-, encodeForest-, decodeForest-) where--import Control.Applicative ((<$>))-import Data.Maybe (fromJust)-import Data.Binary (Binary, get, put)-import Data.Named.Tree hiding (span)---- | An 'IOB' data structure consists of a word with a corresponding--- compound label.-data IOB w a = IOB- { word :: w- , label :: Label a- } deriving (Show)---- | A 'Label' consists of a list of atomic 'Atom' labels.-type Label a = [Atom a]---- | An 'Atom' is the atomic label with additional marker.-data Atom a = B a -- ^ Beginning marker- | I a -- ^ Inside marker - deriving (Show, Eq, Ord, Functor)--instance Binary a => Binary (Atom a) where- put (B x) = put '1' >> put x- put (I x) = put '2' >> put x- get = get >>= \i -> case i of- '1' -> B <$> get- '2' -> I <$> get- _ -> error "Atom Binary instance: invalid code"--push :: Atom a -> IOB w a -> IOB w a-push x (IOB w xs) = IOB w (x:xs)--popMaybe :: IOB w a -> Maybe (Atom a, IOB w a)-popMaybe (IOB w (x:xs)) = Just (x, IOB w xs)-popMaybe (IOB _ []) = Nothing--topMaybe :: IOB w a -> Maybe (Atom a)-topMaybe iob = fst <$> popMaybe iob--pop :: IOB w a -> (Atom a, IOB w a)-pop = fromJust . popMaybe---- top :: IOB w a -> Atom a--- top = fromJust . topMaybe--raw :: Atom a -> a-raw (B x) = x-raw (I x) = x---- isB :: Atom a -> Bool--- isB (B _) = True--- isB _ = False--isI :: Atom a -> Bool-isI (I _) = True-isI _ = False---- | Encode the forest with the IOB method.-encodeForest :: NeForest a w -> [IOB w a]-encodeForest [] = []-encodeForest (x:xs) = encodeTree x ++ encodeForest xs---- | Encode the tree using the IOB method.-encodeTree :: NeTree a w -> [IOB w a]--encodeTree (Node (Left _) []) =- error "encodeTree: label node with no children"-encodeTree (Node (Left e) forest) =- let addLayer (x:xs) = push (B e) x : map (push $ I e) xs- addLayer [] = []- in addLayer (encodeForest forest)--encodeTree (Node (Right _) (_:_)) =- error "encodeTree: word node with children"-encodeTree (Node (Right w) _) = [IOB w []]---- | Decode the forest using the IOB method.-decodeForest :: Eq a => [IOB w a] -> NeForest a w-decodeForest [] = []-decodeForest xs =- tree : decodeForest xs'- where- (chunk, xs') = followTop xs- tree = case topMaybe $ head chunk of- Nothing -> Node (Right . word $ head chunk) []- Just e -> Node (Left $ raw e) (decodeForest $ map rmTop chunk)- rmTop = snd . pop---- | Take iob elements as long as the top label doesn't change. --- Return obtained part together with the rest of iob.-followTop :: Eq a => [IOB w a] -> ([IOB w a], [IOB w a])-followTop [] = error "followTop: empty iob"-followTop (x:xs) =- (x:chunk, rest)- where- (chunk, rest) = span (cond (topMaybe x) . topMaybe) xs- cond (Just a) (Just b) = raw a == raw b && isI b- cond _ _ = False
− Data/Named/Tree.hs
@@ -1,155 +0,0 @@--- | Working with NE trees and forests.--module Data.Named.Tree-( --- * Auxiliary types- NeTree-, NeForest---- * Span-, Span (..)-, leafSpan-, (<>)-, spanSet---- * Trees with span-, span-, spanTree-, spanForest-, unSpanTree-, unSpanForest-, sortTree-, sortForest---- * Utilities-, mapForest-, mapTree-, onLeaf-, onNode-, onEither-, onBoth-, groupForestLeaves-, groupTreeLeaves--, module Data.Tree-) where--import Prelude hiding (span)-import Data.List (sortBy, groupBy)-import Data.Either (rights)-import Data.Ord (comparing)-import Data.Ix (Ix, range)-import Data.Tree-import qualified Data.Set as S---- | A tree with a values in internal nodes and b values in leaves.-type NeTree a b = Tree (Either a b)---- | A forest with a values in internal nodes and b values in leaves.-type NeForest a b = Forest (Either a b)---- | Map function over the leaf value.-onLeaf :: (a -> b) -> Either c a -> Either c b-onLeaf _ (Left x) = Left x-onLeaf f (Right x) = Right (f x)-{-# INLINE onLeaf #-}---- | Map function over the internal node value.-onNode :: (a -> b) -> Either a c -> Either b c-onNode f (Left x) = Left (f x)-onNode _ (Right x) = Right x-{-# INLINE onNode #-}---- | Map the first function over internal node value--- and the second one over leaf value.-onEither :: (a -> c) -> (b -> d) -> Either a b -> Either c d-onEither f _ (Left x) = Left (f x)-onEither _ g (Right x) = Right (g x)-{-# INLINE onEither #-}---- | Map one function over both node and leaf values.-onBoth :: (a -> b) -> Either a a -> Either b b-onBoth f (Left x) = Left (f x)-onBoth f (Right x) = Right (f x)-{-# INLINE onBoth #-}---- | Map function over each tree from the forest.-mapForest :: (a -> b) -> Forest a -> Forest b-mapForest = map . mapTree-{-# INLINE mapForest #-}---- | Map function over the tree.-mapTree :: (a -> b) -> Tree a -> Tree b-mapTree = fmap-{-# INLINE mapTree #-}---- | Group leaves with respect to the given equality function.-groupForestLeaves :: (b -> b -> Bool) -> NeForest a b -> NeForest a [b]-groupForestLeaves f- = concatMap joinLeaves- . groupBy (both isLeaf)- . map (groupTreeLeaves f)- where- joinLeaves [x] = [x]- joinLeaves xs =- let ys = (concat . rights) (map rootLabel xs)- in [Node (Right ys') [] | ys' <- groupBy f ys]- both g x y = g x && g y- isLeaf (Node (Right _) []) = True- isLeaf _ = False---- | Group leaves with respect to the given equality function.-groupTreeLeaves :: (b -> b -> Bool) -> NeTree a b -> NeTree a [b]-groupTreeLeaves f (Node v xs) = Node (fmap (:[]) v) (groupForestLeaves f xs)---- | Spanning of a tree.-data Span w = Span- { beg :: w- , end :: w }- deriving (Show, Eq, Ord)---- | Make span for a leaf node.-leafSpan :: w -> Span w-leafSpan i = Span i i---- | Minimum span overlapping both input spans.-(<>) :: Ord w => Span w -> Span w -> Span w-Span p q <> Span p' q' = Span (min p p') (max q q')-{-# INLINE (<>) #-}---- | Set of positions covered by the span.-spanSet :: Ix w => Span w -> S.Set w-spanSet s = S.fromList $ range (beg s, end s)---- | Get span of the span-annotated tree.-span :: Tree (a, Span w) -> Span w-span = snd . rootLabel---- | Annotate tree nodes with spanning info given the function--- which assignes indices to leaf nodes.-spanTree :: Ord w => Tree (Either n w) -> Tree (Either n w, Span w)-spanTree (Node (Right k) []) = Node (Right k, leafSpan k) []-spanTree (Node k ts) =- let us = spanForest ts- s = foldl1 (<>) (map span us)- in Node (k, s) us---- | Annotate forest nodes with spanning info.-spanForest :: Ord w => Forest (Either n w) -> Forest (Either n w, Span w)-spanForest = map spanTree---- | Remove span annotations from the tree.-unSpanTree :: Tree (k, Span w) -> Tree k-unSpanTree = fmap fst---- | Remove span annotations from the forest.-unSpanForest :: Forest (k, Span w) -> Forest k-unSpanForest = map unSpanTree---- | Sort the tree with respect to spanning info.-sortTree :: Ord w => Tree (k, Span w) -> Tree (k, Span w)-sortTree (Node x ts) = Node x (sortForest ts)---- | Sort the forest with respect to spanning info.-sortForest :: Ord w => Forest (k, Span w) -> Forest (k, Span w)-sortForest = sortBy (comparing span) . map sortTree
− Text/Named/Enamex.hs
@@ -1,164 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{- |- Parsing text in the Enamex data format. Each node is enclosed between- opening and closing tags with tag name representing the label and contents- representing children of the node. Both leaf and label values should be- escaped by prepending the \\ character before special >, <, \\ and space- characters.-- Example:-->>> :m Text.Named.Enamex Data.Named.Tree Data.Text.Lazy->>> let drawIt = putStr . drawForest . mapForest show . parseForest->>> drawIt $ pack "<x>w1.1\\ w1.2</x> <y><z>w2</z> w3</y>"-Left "x"-|-`- Right "w1.1 w1.2"-,-Left "y"-|-+- Left "z"-| |-| `- Right "w2"-|-`- Right "w3"--}--module Text.Named.Enamex-(--- * Parsing- parseForest-, parseEnamex---- * Printing-, showForest-, showEnamex-) where--import Control.Applicative-import Control.Monad ((<=<), when)-import Data.Monoid-import Data.Attoparsec.Text.Lazy-import Data.List (intersperse)-import Data.Function (on)-import qualified Data.Text as T-import qualified Data.Text.Lazy as L-import qualified Data.Text.Lazy.Builder as L-import qualified Data.Named.Tree as Tr--pForest :: Parser (Tr.NeForest T.Text T.Text)-pForest = pTree `sepBy` (space *> skipSpace)--pTree :: Parser (Tr.NeTree T.Text T.Text)-pTree = pNode- <|> pLeaf--pLeaf :: Parser (Tr.NeTree T.Text T.Text)-pLeaf = Tr.Node <$> (Right <$> pWord) <*> pure []--pNode :: Parser (Tr.NeTree T.Text T.Text)-pNode = do- x <- pOpenTag- kids <- pForest- x' <- pCloseTag- when (x /= x') (fail "Tag start/end mismatch")- return $ Tr.Node (Left x) kids--pOpenTag :: Parser T.Text-pOpenTag = "<" .*> pWord <*. ">"--pCloseTag :: Parser T.Text-pCloseTag = "</" .*> pWord <*. ">"--pWord :: Parser T.Text-pWord =- unEscape <$> scan False special- where- special False c =- case c == ' ' || c == '<' || c == '>' of- True -> Nothing- False -> if c == '\\'- then Just True- else Just False- special True _ = Just False---- | TODO: Use lazy text builder to avoid slowness in the pessimistic case.-unEscape :: T.Text -> T.Text-unEscape xs = x `T.append` case drop1 rest of- Just (y, ys) -> y `T.cons` unEscape ys- Nothing -> ""- where- drop1 = T.uncons <=< return . snd <=< T.uncons- (x, rest) = T.breakOn "\\" xs ---- | TODO: Use lazy text builder to avoid slowness in the pessimistic case.-escape :: T.Text -> T.Text-escape x = case T.uncons z of- Nothing -> y- Just (c, q) -> y- `T.append` ('\\'- `T.cons` (c- `T.cons` escape q))- where- (y, z) = T.break special x- special c = c == ' ' || c == '<' || c == '>' || c == '\\'---- | Parse the enamex forest.-parseForest :: L.Text -> Tr.NeForest T.Text T.Text-parseForest = either error id . eitherResult . parse (pForest <* endOfInput)---- | Parse the enamex file.-parseEnamex :: L.Text -> [Tr.NeForest T.Text T.Text]-parseEnamex = map parseForest . L.lines--data Tag = Open | Close | Body---- | Function which determines between which two tag elements a space--- character should be inserted.-noSpace :: Tag -> Tag -> Bool-noSpace Open _ = True-noSpace Body Close = True-noSpace Close Close = True-noSpace _ _ = False---- | We define our own groupBy because the standard version from Data.List--- assumes that the predicate is transitive.-groupBy :: (a -> a -> Bool) -> [a] -> [[a]]-groupBy p (x : y : xs)- | p x y = join x $ groupBy p (y : xs)- | otherwise = [x] : groupBy p (y : xs)- where- join z (zs : zss) = (z : zs) : zss- join z [] = [[z]]-groupBy _ [x] = [[x]]-groupBy _ [] = []--buildForest :: Tr.NeForest t t -> [(t, Tag)]-buildForest = concat . map buildTree--buildTree :: Tr.NeTree t t -> [(t, Tag)]-buildTree (Tr.Node (Left x) ts) = (x, Open) : buildForest ts ++ [(x, Close)]-buildTree (Tr.Node (Right x) _) = [(x, Body)]--buildStream :: [(T.Text, Tag)] -> L.Builder-buildStream- = mconcat . intersperse " "- . map (mconcat . map buildTag)- . groupBy (noSpace `on` snd)--buildTag :: (T.Text, Tag) -> L.Builder-buildTag (x, tag) = case tag of- Open -> "<" `mappend` y `mappend` ">"- Close -> "</" `mappend` y `mappend` ">"- _ -> y- where- y = L.fromText (escape x)---- | Show the forest.-showForest :: Tr.NeForest T.Text T.Text -> L.Text-showForest = L.toLazyText . buildStream . buildForest---- | Show the enamex file.-showEnamex :: [Tr.NeForest T.Text T.Text] -> L.Text-showEnamex = L.toLazyText . mconcat . map (L.fromLazyText . showForest)
+ changelog view
@@ -0,0 +1,9 @@+-*-change-log-*-++0.6.1 Jun 2016+ * Add stack.yaml+ * Add README+ * Add `Applicative` instances where needed (as a result of the `Applicative => Monad` proposal)++0.6.0 Jul 2014+ * Add `concatForestLeaves` function (inverse of `groupForestLeaves`)
data-named.cabal view
@@ -1,5 +1,5 @@ name: data-named-version: 0.5.2+version: 0.6.1 synopsis: Data types for named entities description: The library provides data types which can be used to represent@@ -26,13 +26,17 @@ homepage: https://github.com/kawu/data-named build-type: Simple +extra-source-files: changelog+ library+ hs-source-dirs: src+ build-depends:- base >= 4 && < 5- , containers- , text- , attoparsec- , binary+ base >= 4.8 && < 5+ , containers >= 0.5 && < 0.6+ , text >= 1.1 && < 1.3+ , attoparsec >= 0.12 && < 0.14+ , binary >= 0.7 && < 0.9 exposed-modules: Data.Named.Tree
+ src/Data/Named/Graph.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE DeriveFunctor #-}++-- | Implementation of a graph with each internal node identified by a+-- unique key and each leaf represented by a position in the sentence.++module Data.Named.Graph+( Graph (..)+, mkGraph+, edges+, roots+, toForest+) where++import Data.Either (lefts, rights)+import Data.Ix (Ix, inRange, range)+import qualified Data.Map as M+import qualified Data.Set as S+import Prelude hiding (span)++import Data.Named.Tree++-- | A graph over a sentence.+data Graph n w = Graph+ { bounds :: (w, w)+ , edgeMap :: M.Map n [Either n w] }++-- | Make a graph given the bounds and list of edges.+mkGraph :: (Ord n, Ix w) => (w, w) -> [(n, [Either n w])] -> Graph n w+mkGraph bs =+ Graph bs . M.fromList . map check+ where+ check (k, ks)+ | null ks =+ error "mkGraph: Left, internal node without output edges"+ | any (not . inRange bs) (rights ks) =+ error "mkGraph: Right, leaf node outside of bounds"+ | otherwise = (k, ks)++-- | Get keys of adjacent nodes for the given node key.+edges :: Ord n => Graph n w -> n -> [Either n w]+edges g k = case M.lookup k (edgeMap g) of+ Nothing -> error "edges: key not in the map"+ Just v -> v+{-# INLINE edges #-}++-- | Return all graph roots (i.e. nodes with no parents).+roots :: Ord n => Graph n w -> [n]+roots g =+ let desc = S.fromList . lefts . concat . M.elems $ edgeMap g+ in [k | k <- M.keys (edgeMap g), not (k `S.member` desc)]++generate :: Ord n => Graph n w -> Either n w -> NeTree n w+generate g (Left k) = Node (Left k) (map (generate g) (edges g k))+generate _ w = Node w []++prune :: Ord w => NeForest n w -> NeForest n w+prune = unSpanForest . run . chop . sortForest . spanForest++-- | Combine the disjoint forest with the list of words.+-- Discontinuities will be patched with no trace.+addWords :: Ix w => (w, w) -> NeForest n w -> NeForest n w+addWords (p, q) [] = [Node (Right x) [] | x <- range (p, q)]+addWords (p, q) ts+ = unSpanForest . subForest+ . sortTree . fillTree+ . dummyRoot+ . spanForest $ ts+ where+ dummyRoot = Node (undefined, Span p q)+ mkLeaf k = Node (Right k, leafSpan k) []++ fillForest = map fillTree+ fillTree (Node n []) = Node n []+ fillTree (Node (k, s) us) =+ let m = spanSet s S.\\ S.unions (map (spanSet . span) us)+ in Node (k, s) (fillForest us ++ map mkLeaf (S.toList m))++-- | Transform a graph into a disjoint forest, i.e. a forest with+-- no mutually overlapping trees.+-- The process is lossy, discontinuity and overlapping cannot+-- be represented with the `NeForest` data type.+toForest :: (Ord n, Ix w) => Graph n w -> NeForest n w+toForest g = addWords (bounds g) . prune . map (generate g . Left) . roots $ g++-- | A stateful monad for forest pruning.+newtype RanM w a = RanM { runRanM :: Maybe w -> (a, Maybe w) }++deriving instance Functor (RanM w)++instance Applicative (RanM w) where+ pure x = RanM $ \s -> (x, s)+ RanM v <*> RanM w = RanM $+ \s -> case v s of+ (f, s') -> case w s' of+ (x, s'') -> (f x, s'')++instance Monad (RanM w) where+ return x = RanM $ \s -> (x, s)+ RanM v >>= f = RanM $ \s -> case v s of (x, s') -> runRanM (f x) s'++run :: RanM w a -> a+run act = fst (runRanM act Nothing)++contains :: Ord w => w -> RanM w Bool+contains k = RanM $ \m -> case m of+ Just x -> (k <= x, m)+ Nothing -> (False, m)++include :: w -> RanM w ()+include k = RanM $ \_ -> ((), Just k)++chop :: Ord w => Forest (k, Span w) -> RanM w (Forest (k, Span w))+chop [] = return []+chop (Node (k, s) ts : us) = do+ visited <- contains (end s)+ if visited then+ chop us+ else do+ as <- chop ts+ include (end s)+ bs <- chop us+ return (Node (k, s) as : bs)
+ src/Data/Named/IOB.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE DeriveFunctor #-}++{- |+ IOB encoding method extended to forests.++ Example:++>>> :m Data.Named.IOB Data.Named.Tree Text.Named.Enamex Data.Text.Lazy+>>> let enamex = pack "<x>w1.1\\ w1.2</x> w2 <y><z>w3</z> w4</y>"++>>> putStr . drawForest . mapForest show . parseForest $ enamex+Left "x"+|+`- Right "w1.1 w1.2"+,+Right "w2"+,+Left "y"+|++- Left "z"+| |+| `- Right "w3"+|+`- Right "w4"++>>> mapM_ print . encodeForest . parseForest $ enamex+IOB {word = "w1.1 w1.2", label = [B "x"]}+IOB {word = "w2", label = []}+IOB {word = "w3", label = [B "y",B "z"]}+IOB {word = "w4", label = [I "y"]}+-}++module Data.Named.IOB+( IOB (..)+, Label+, Atom (..)+, encodeForest+, decodeForest+) where++import Control.Applicative ((<$>))+import Data.Maybe (fromJust)+import Data.Binary (Binary, get, put)+import Data.Named.Tree hiding (span)++-- | An 'IOB' data structure consists of a word with a corresponding+-- compound label.+data IOB w a = IOB+ { word :: w+ , label :: Label a+ } deriving (Show)++-- | A 'Label' consists of a list of atomic 'Atom' labels.+type Label a = [Atom a]++-- | An 'Atom' is the atomic label with additional marker.+data Atom a = B a -- ^ Beginning marker+ | I a -- ^ Inside marker + deriving (Show, Eq, Ord, Functor)++instance Binary a => Binary (Atom a) where+ put (B x) = put '1' >> put x+ put (I x) = put '2' >> put x+ get = get >>= \i -> case i of+ '1' -> B <$> get+ '2' -> I <$> get+ _ -> error "Atom Binary instance: invalid code"++push :: Atom a -> IOB w a -> IOB w a+push x (IOB w xs) = IOB w (x:xs)++popMaybe :: IOB w a -> Maybe (Atom a, IOB w a)+popMaybe (IOB w (x:xs)) = Just (x, IOB w xs)+popMaybe (IOB _ []) = Nothing++topMaybe :: IOB w a -> Maybe (Atom a)+topMaybe iob = fst <$> popMaybe iob++pop :: IOB w a -> (Atom a, IOB w a)+pop = fromJust . popMaybe++-- top :: IOB w a -> Atom a+-- top = fromJust . topMaybe++raw :: Atom a -> a+raw (B x) = x+raw (I x) = x++-- isB :: Atom a -> Bool+-- isB (B _) = True+-- isB _ = False++isI :: Atom a -> Bool+isI (I _) = True+isI _ = False++-- | Encode the forest with the IOB method.+encodeForest :: NeForest a w -> [IOB w a]+encodeForest [] = []+encodeForest (x:xs) = encodeTree x ++ encodeForest xs++-- | Encode the tree using the IOB method.+encodeTree :: NeTree a w -> [IOB w a]++encodeTree (Node (Left _) []) =+ error "encodeTree: label node with no children"+encodeTree (Node (Left e) forest) =+ let addLayer (x:xs) = push (B e) x : map (push $ I e) xs+ addLayer [] = []+ in addLayer (encodeForest forest)++encodeTree (Node (Right _) (_:_)) =+ error "encodeTree: word node with children"+encodeTree (Node (Right w) _) = [IOB w []]++-- | Decode the forest using the IOB method.+decodeForest :: Eq a => [IOB w a] -> NeForest a w+decodeForest [] = []+decodeForest xs =+ tree : decodeForest xs'+ where+ (chunk, xs') = followTop xs+ tree = case topMaybe $ head chunk of+ Nothing -> Node (Right . word $ head chunk) []+ Just e -> Node (Left $ raw e) (decodeForest $ map rmTop chunk)+ rmTop = snd . pop++-- | Take iob elements as long as the top label doesn't change. +-- Return obtained part together with the rest of iob.+followTop :: Eq a => [IOB w a] -> ([IOB w a], [IOB w a])+followTop [] = error "followTop: empty iob"+followTop (x:xs) =+ (x:chunk, rest)+ where+ (chunk, rest) = span (cond (topMaybe x) . topMaybe) xs+ cond (Just a) (Just b) = raw a == raw b && isI b+ cond _ _ = False
+ src/Data/Named/Tree.hs view
@@ -0,0 +1,166 @@+-- | Working with NE trees and forests.++module Data.Named.Tree+( +-- * Auxiliary types+ NeTree+, NeForest++-- * Span+, Span (..)+, leafSpan+, (<>)+, spanSet++-- * Trees with span+, span+, spanTree+, spanForest+, unSpanTree+, unSpanForest+, sortTree+, sortForest++-- * Utilities+, mapForest+, mapTree+, onLeaf+, onNode+, onEither+, onBoth+, groupForestLeaves+, groupTreeLeaves+, concatForestLeaves+, concatTreeLeaves++, module Data.Tree+) where++import Prelude hiding (span)+import Data.List (sortBy, groupBy)+import Data.Either (rights)+import Data.Ord (comparing)+import Data.Ix (Ix, range)+import Data.Tree+import qualified Data.Set as S++-- | A tree with a values in internal nodes and b values in leaves.+type NeTree a b = Tree (Either a b)++-- | A forest with a values in internal nodes and b values in leaves.+type NeForest a b = Forest (Either a b)++-- | Map function over the leaf value.+onLeaf :: (a -> b) -> Either c a -> Either c b+onLeaf _ (Left x) = Left x+onLeaf f (Right x) = Right (f x)+{-# INLINE onLeaf #-}++-- | Map function over the internal node value.+onNode :: (a -> b) -> Either a c -> Either b c+onNode f (Left x) = Left (f x)+onNode _ (Right x) = Right x+{-# INLINE onNode #-}++-- | Map the first function over internal node value+-- and the second one over leaf value.+onEither :: (a -> c) -> (b -> d) -> Either a b -> Either c d+onEither f _ (Left x) = Left (f x)+onEither _ g (Right x) = Right (g x)+{-# INLINE onEither #-}++-- | Map one function over both node and leaf values.+onBoth :: (a -> b) -> Either a a -> Either b b+onBoth f (Left x) = Left (f x)+onBoth f (Right x) = Right (f x)+{-# INLINE onBoth #-}++-- | Map function over each tree from the forest.+mapForest :: (a -> b) -> Forest a -> Forest b+mapForest = map . mapTree+{-# INLINE mapForest #-}++-- | Map function over the tree.+mapTree :: (a -> b) -> Tree a -> Tree b+mapTree = fmap+{-# INLINE mapTree #-}++-- | Group leaves with respect to the given equality function.+groupForestLeaves :: (b -> b -> Bool) -> NeForest a b -> NeForest a [b]+groupForestLeaves f+ = concatMap joinLeaves+ . groupBy (both isLeaf)+ . map (groupTreeLeaves f)+ where+ joinLeaves [x] = [x]+ joinLeaves xs =+ let ys = (concat . rights) (map rootLabel xs)+ in [Node (Right ys') [] | ys' <- groupBy f ys]+ both g x y = g x && g y+ isLeaf (Node (Right _) []) = True+ isLeaf _ = False++-- | Group leaves with respect to the given equality function.+groupTreeLeaves :: (b -> b -> Bool) -> NeTree a b -> NeTree a [b]+groupTreeLeaves f (Node v xs) = Node (fmap (:[]) v) (groupForestLeaves f xs)++-- | Group leaves with respect to the given equality function.+concatForestLeaves :: NeForest a [b] -> NeForest a b+concatForestLeaves = concatMap concatTreeLeaves++-- | Group leaves with respect to the given equality function.+concatTreeLeaves :: NeTree a [b] -> NeForest a b+concatTreeLeaves (Node (Left x) xs) = [Node (Left x) (concatForestLeaves xs)]+concatTreeLeaves (Node (Right xs) _) = [Node (Right x) [] | x <- xs]++-- | Spanning of a tree.+data Span w = Span+ { beg :: w+ , end :: w }+ deriving (Show, Eq, Ord)++-- | Make span for a leaf node.+leafSpan :: w -> Span w+leafSpan i = Span i i++-- | Minimum span overlapping both input spans.+(<>) :: Ord w => Span w -> Span w -> Span w+Span p q <> Span p' q' = Span (min p p') (max q q')+{-# INLINE (<>) #-}++-- | Set of positions covered by the span.+spanSet :: Ix w => Span w -> S.Set w+spanSet s = S.fromList $ range (beg s, end s)++-- | Get span of the span-annotated tree.+span :: Tree (a, Span w) -> Span w+span = snd . rootLabel++-- | Annotate tree nodes with spanning info given the function+-- which assignes indices to leaf nodes.+spanTree :: Ord w => Tree (Either n w) -> Tree (Either n w, Span w)+spanTree (Node (Right k) []) = Node (Right k, leafSpan k) []+spanTree (Node k ts) =+ let us = spanForest ts+ s = foldl1 (<>) (map span us)+ in Node (k, s) us++-- | Annotate forest nodes with spanning info.+spanForest :: Ord w => Forest (Either n w) -> Forest (Either n w, Span w)+spanForest = map spanTree++-- | Remove span annotations from the tree.+unSpanTree :: Tree (k, Span w) -> Tree k+unSpanTree = fmap fst++-- | Remove span annotations from the forest.+unSpanForest :: Forest (k, Span w) -> Forest k+unSpanForest = map unSpanTree++-- | Sort the tree with respect to spanning info.+sortTree :: Ord w => Tree (k, Span w) -> Tree (k, Span w)+sortTree (Node x ts) = Node x (sortForest ts)++-- | Sort the forest with respect to spanning info.+sortForest :: Ord w => Forest (k, Span w) -> Forest (k, Span w)+sortForest = sortBy (comparing span) . map sortTree
+ src/Text/Named/Enamex.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE OverloadedStrings #-}++{- |+ Parsing text in the Enamex data format. Each node is enclosed between+ opening and closing tags with tag name representing the label and contents+ representing children of the node. Both leaf and label values should be+ escaped by prepending the \\ character before special >, <, \\ and space+ characters.++ Example:++>>> :m Text.Named.Enamex Data.Named.Tree Data.Text.Lazy+>>> let drawIt = putStr . drawForest . mapForest show . parseForest+>>> drawIt $ pack "<x>w1.1\\ w1.2</x> <y><z>w2</z> w3</y>"+Left "x"+|+`- Right "w1.1 w1.2"+,+Left "y"+|++- Left "z"+| |+| `- Right "w2"+|+`- Right "w3"+-}++module Text.Named.Enamex+(+-- * Parsing+ parseForest+, parseEnamex++-- * Printing+, showForest+, showEnamex+) where++import Control.Applicative+import Control.Monad (when, (<=<))+-- import Data.Monoid+import Data.Attoparsec.Text.Lazy+import Data.Function (on)+import Data.List (intersperse)+import qualified Data.Named.Tree as Tr+import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Builder as L++pForest :: Parser (Tr.NeForest T.Text T.Text)+pForest = pTree `sepBy` (space *> skipSpace)++pTree :: Parser (Tr.NeTree T.Text T.Text)+pTree = pNode+ <|> pLeaf++pLeaf :: Parser (Tr.NeTree T.Text T.Text)+pLeaf = Tr.Node <$> (Right <$> pWord) <*> pure []++pNode :: Parser (Tr.NeTree T.Text T.Text)+pNode = do+ x <- pOpenTag+ kids <- pForest+ x' <- pCloseTag+ when (x /= x') (fail "Tag start/end mismatch")+ return $ Tr.Node (Left x) kids++pOpenTag :: Parser T.Text+pOpenTag = "<" *> pWord <* ">"++pCloseTag :: Parser T.Text+pCloseTag = "</" *> pWord <* ">"++pWord :: Parser T.Text+pWord =+ unEscape <$> scan False special+ where+ special False c =+ case c == ' ' || c == '<' || c == '>' of+ True -> Nothing+ False -> if c == '\\'+ then Just True+ else Just False+ special True _ = Just False++-- | TODO: Use lazy text builder to avoid slowness in the pessimistic case.+unEscape :: T.Text -> T.Text+unEscape xs = x `T.append` case drop1 rest of+ Just (y, ys) -> y `T.cons` unEscape ys+ Nothing -> ""+ where+ drop1 = T.uncons <=< return . snd <=< T.uncons+ (x, rest) = T.breakOn "\\" xs++-- | TODO: Use lazy text builder to avoid slowness in the pessimistic case.+escape :: T.Text -> T.Text+escape x = case T.uncons z of+ Nothing -> y+ Just (c, q) -> y+ `T.append` ('\\'+ `T.cons` (c+ `T.cons` escape q))+ where+ (y, z) = T.break special x+ special c = c == ' ' || c == '<' || c == '>' || c == '\\'++-- | Parse the enamex forest.+parseForest :: L.Text -> Tr.NeForest T.Text T.Text+parseForest = either error id . eitherResult . parse (pForest <* endOfInput)++-- | Parse the enamex file.+parseEnamex :: L.Text -> [Tr.NeForest T.Text T.Text]+parseEnamex = map parseForest . L.lines++data Tag = Open | Close | Body++-- | Function which determines between which two tag elements a space+-- character should be inserted.+noSpace :: Tag -> Tag -> Bool+noSpace Open _ = True+noSpace Body Close = True+noSpace Close Close = True+noSpace _ _ = False++-- | We define our own groupBy because the standard version from Data.List+-- assumes that the predicate is transitive.+groupBy :: (a -> a -> Bool) -> [a] -> [[a]]+groupBy p (x : y : xs)+ | p x y = join x $ groupBy p (y : xs)+ | otherwise = [x] : groupBy p (y : xs)+ where+ join z (zs : zss) = (z : zs) : zss+ join z [] = [[z]]+groupBy _ [x] = [[x]]+groupBy _ [] = []++buildForest :: Tr.NeForest t t -> [(t, Tag)]+buildForest = concat . map buildTree++buildTree :: Tr.NeTree t t -> [(t, Tag)]+buildTree (Tr.Node (Left x) ts) = (x, Open) : buildForest ts ++ [(x, Close)]+buildTree (Tr.Node (Right x) _) = [(x, Body)]++buildStream :: [(T.Text, Tag)] -> L.Builder+buildStream+ = mconcat . intersperse " "+ . map (mconcat . map buildTag)+ . groupBy (noSpace `on` snd)++buildTag :: (T.Text, Tag) -> L.Builder+buildTag (x, tag) = case tag of+ Open -> "<" `mappend` y `mappend` ">"+ Close -> "</" `mappend` y `mappend` ">"+ _ -> y+ where+ y = L.fromText (escape x)++-- | Show the forest.+showForest :: Tr.NeForest T.Text T.Text -> L.Text+showForest = L.toLazyText . buildStream . buildForest++-- | Show the enamex file.+showEnamex :: [Tr.NeForest T.Text T.Text] -> L.Text+showEnamex = L.toLazyText . mconcat . map (L.fromLazyText . showForest)