diff --git a/Data/Named/Graph.hs b/Data/Named/Graph.hs
new file mode 100644
--- /dev/null
+++ b/Data/Named/Graph.hs
@@ -0,0 +1,99 @@
+-- | Implementation of a graph with each node identified by a unique key.
+-- It is a provisional module and it might be replaced by the standard
+-- graph from containers package in the future.
+
+module Data.Named.Graph
+( Graph (..)
+, mkGraph
+, node
+, edges
+, roots
+, toTree
+, toKeyTree
+, toForestWith
+, toForest
+) where
+
+import qualified Data.Set as S
+import qualified Data.Map as M
+import qualified Data.Tree as T
+import Data.List (mapAccumL, foldl', sortBy)
+import Data.Maybe (mapMaybe, fromJust)
+import Data.Ord (comparing)
+
+-- | A graph.
+data Graph k v = Graph
+    { nodeMap :: M.Map k v
+    , edgeMap :: M.Map k [k] }
+
+-- | Make a graph from a list of (key, value, [children keys]) tuples.
+mkGraph :: Ord k => [(k, v, [k])] -> Graph k v
+mkGraph xs = 
+    Graph ns es
+  where
+    ns = M.fromList [(k, v)  | (k, v, _)  <- xs]
+    es = M.fromList [(k, ks) | (k, _, ks) <- xs]
+
+-- | Get node with the given key.
+node :: (Show k, Ord k) => Graph k v -> k -> v
+node g k = case M.lookup k (nodeMap g) of
+    Nothing -> error $ "node: key " ++ show k ++ " not in the nodes map"
+    Just v  -> v
+{-# INLINE node #-}
+
+-- | Get keys of adjacent nodes for the given node key.
+edges :: (Show k, Ord k) => Graph k v -> k -> [k]
+edges g k = case M.lookup k (edgeMap g) of
+    Nothing -> error $ "edges: key " ++ show k ++ " not in the edges map"
+    Just v  -> v
+{-# INLINE edges #-}
+
+-- | Return all graph roots (i.e. nodes with no parents).
+roots :: Ord k => Graph k v -> [k]
+roots g =
+    [ k
+    | (k, _) <- M.assocs (nodeMap g)
+    , not (k `S.member` desc) ]
+  where
+    desc = S.fromList . concat . M.elems $ edgeMap g
+
+-- | Make a tree rooted in the node with respect to the graph.
+toTree :: (Show k, Ord k) => Graph k v -> k -> T.Tree v
+toTree g = fmap (node g) . toKeyTree g
+
+-- | Make a key tree rooted in the node with respect to the graph.
+toKeyTree :: (Show k, Ord k) => Graph k v -> k -> T.Tree k
+toKeyTree g k = T.Node k
+    [ toKeyTree g k'
+    | k' <- edges g k ]
+
+-- | Transform graph into a forest given the priority function.
+-- That is, trees with higher priorities will be taken first,
+-- while those with lower priorities might be trimmed down
+-- (since we don't want to have nodes with multiple parents in
+-- the resulting forest).
+toForestWith :: (Show k, Ord k, Ord a)
+             => (T.Tree v -> a) -> Graph k v -> T.Forest v
+toForestWith pr g = map valTr . snd $
+    mapAccumL trim S.empty sortedTrees
+  where
+    valTr = fmap (node g) -- Make value tree from a key tree
+    trees = map (toKeyTree g) (roots g)
+    sortedTrees =
+        let f = pr . valTr
+        in  sortBy (comparing f) trees
+
+-- | Transform graph into a forest. It removes duplicate
+-- nodes from trees chosing trees in an arbitrary order.
+toForest :: (Show k, Ord k) => Graph k v -> T.Forest v
+toForest = toForestWith $ const (0 :: Int)
+
+trim :: Ord k => S.Set k -> T.Tree k -> (S.Set k, T.Tree k)
+trim visited tree =
+    (visited', tree')
+  where
+    tree'    = fromJust (doIt tree)
+    visited' = foldl' (flip S.insert) visited (T.flatten tree')
+    doIt (T.Node x ts)
+        | x `S.member` visited = Nothing
+        | otherwise = Just $ T.Node x (mapMaybe doIt ts)
diff --git a/Data/Named/IOB.hs b/Data/Named/IOB.hs
new file mode 100644
--- /dev/null
+++ b/Data/Named/IOB.hs
@@ -0,0 +1,127 @@
+{- |
+    IOB encoding method extended to forests.
+
+    Example:
+
+>>> :m Data.Tree Data.Text Text.Named.Enamex Data.Named.IOB
+>>> let enamex = pack "<x>w1.1\\ w1.2</x> w2 <y><z>w3</z> w4</y>"
+>>> let parseIt = fmap (mapTwo id id . fmap unpack) . parseForest
+
+>>> putStr . drawForest . fmap (fmap show) . parseIt $ enamex
+Left "x"
+|
+`- Right "w1.1 w1.2"
+,
+Right "w2"
+,
+Left "y"
+|
++- Left "z"
+|  |
+|  `- Right "w3"
+|
+`- Right "w4"
+
+>>> mapM_ print . encodeForest . parseIt $ 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.Tree
+
+-- | 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)
+
+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 :: Forest (Either a w) -> [IOB w a]
+encodeForest [] = []
+encodeForest (x:xs) = encodeTree x ++ encodeForest xs
+
+-- | Encode the tree using the IOB method.
+encodeTree :: Tree (Either 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] -> Forest (Either 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
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, IPI PAN
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,4 @@
+#! /usr/bin/env runhaskell
+
+> import Distribution.Simple
+> main = defaultMain
diff --git a/Text/Named/Enamex.hs b/Text/Named/Enamex.hs
new file mode 100644
--- /dev/null
+++ b/Text/Named/Enamex.hs
@@ -0,0 +1,102 @@
+{-# 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 ' ' (space), '>',
+    '<' and '\' characters.
+
+    Example:
+
+>>> :m Data.Tree Data.Text Text.Named.Enamex
+>>> let drawIt = putStr . drawForest . fmap (fmap unpack) . parseForest
+>>> drawIt $ pack "<x>w1.1\\ w1.2</x> <y><z>w2</z> w3</y>"
+x
+|
+`- w1.1 w1.2
+,
+y
+|
++- z
+|  |
+|  `- w2
+|
+`- w3
+-}
+
+module Text.Named.Enamex
+( parseForest
+, parseEnamex
+, mapTwo
+) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Attoparsec.Text
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as L
+import qualified Data.Tree as Tree
+
+
+type Tree = Tree.Tree T.Text
+type Forest = Tree.Forest T.Text
+
+-- | Map the first function over internal nodes
+-- and the second one over leaves.
+mapTwo :: (a -> b) -> (a -> c) -> Tree.Tree a -> Tree.Tree (Either b c)
+mapTwo _ g (Tree.Node x [])   = Tree.Node (Right $ g x) []
+mapTwo f g (Tree.Node x kids) = Tree.Node (Left $ f x) (map (mapTwo f g) kids)
+
+pForest :: Parser Forest
+pForest = pTree `sepBy` (space *> skipSpace)
+
+pTree :: Parser Tree
+pTree = pNode
+    <|> pLeaf
+
+pLeaf :: Parser Tree
+pLeaf = Tree.Node <$> pWord <*> pure []
+
+pNode :: Parser Tree
+pNode = do
+    x    <- pOpenTag
+    kids <- pForest
+    x'   <- pCloseTag
+    when (x /= x') (fail "Tag start/end mismatch")
+    return $ Tree.Node 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 
+
+-- | Parse the enamex forest.
+parseForest :: T.Text -> Forest
+parseForest = either error id . parseOnly (pForest <* endOfInput)
+
+-- | Parse the enamex file.
+parseEnamex :: L.Text -> [Forest]
+parseEnamex = map (parseForest . L.toStrict) . L.lines
diff --git a/data-named.cabal b/data-named.cabal
new file mode 100644
--- /dev/null
+++ b/data-named.cabal
@@ -0,0 +1,45 @@
+name:               data-named
+version:            0.1.0
+synopsis:           Data types for named entities
+description:
+    The library provides data types which can be used to represent
+    forest structures with labels stored in internal nodes and
+    words kept in leaves.  In particular, those types are well suited
+    for representing the layer of named entities (NEs).
+    .
+    The IOB method is implemented in the Data.Named.IOB module and can
+    be used to translate between a forest of entities and a sequence
+    of compound IOB labels.  This method can be used together with a
+    sequence classifier to indirectly model forest structures.
+    .
+    The Data.Named.Graph module can be used to represent more general,
+    graph structures of entities.  The module provides also a lossy
+    conversion from a DAG to a forest of entities.
+license:            BSD3
+license-file:       LICENSE
+cabal-version:      >= 1.6
+copyright:          Copyright (c) 2012 IPI PAN
+author:             Jakub Waszczuk
+maintainer:         waszczuk.kuba@gmail.com
+stability:          experimental
+category:           Natural Language Processing
+homepage:           https://github.com/kawu/data-named
+build-type:         Simple
+
+library
+    build-depends:
+        base >= 4 && < 5
+      , containers
+      , text
+      , attoparsec
+
+    exposed-modules:
+        Data.Named.Graph
+      , Data.Named.IOB
+      , Text.Named.Enamex
+
+    ghc-options: -Wall
+
+source-repository head
+    type: git
+    location: git://github.com/kawu/data-named.git
