diff --git a/Data/NKJP/Morphosyntax.hs b/Data/NKJP/Morphosyntax.hs
deleted file mode 100644
--- a/Data/NKJP/Morphosyntax.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-{-# LANGUAGE DeriveFunctor #-}
-
--- | Data types for the morphosytnax layer of the NKJP corpus.
-
-module Data.NKJP.Morphosyntax
-( Para (..)
-, Sent (..)
-, Seg (..)
-, Lex (..)
-) where
-
--- | A paragraph.
-data Para t = Para
-    { paraID    :: t
-    , sentences :: [Sent t] }
-    deriving (Show, Functor)
-
--- | A sentence.
-data Sent t = Sent
-    { sentID    :: t
-    , segments  :: [Seg t] }
-    deriving (Show, Functor)
-
--- | A segment.
-data Seg t = Seg
-    { segID     :: t 
-    , orth      :: t
-    , nps       :: Bool
-    , lexs      :: [Lex t]
-    , choice    :: (t, t) }
-    deriving (Show, Functor)
-
--- | A lexciacal entry, potential interpretation of the segment.
-data Lex t = Lex
-    { lexID     :: t
-    , base      :: t
-    , ctag      :: t
-    , msds      :: [(t, t)] }
-    deriving (Show, Functor)
diff --git a/Data/NKJP/Named.hs b/Data/NKJP/Named.hs
deleted file mode 100644
--- a/Data/NKJP/Named.hs
+++ /dev/null
@@ -1,108 +0,0 @@
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE RecordWildCards #-}
-
--- | Data types for the named entities layer of the NKJP corpus.
-
-module Data.NKJP.Named
-( Cert (..)
-, Ptr (..)
-, Deriv (..)
-, Para (..)
-, Sent (..)
-, NE (..)
-, mkForest
-) where
-
-import Data.Named.Graph (toForest, mkGraph)
-import Data.Named.Tree (mapTrees)
-import Data.NKJP.Morphosyntax (Seg, segID)
-import qualified Data.Map as M
-import qualified Data.Tree as T
-
--- | A certainty of an annotator.
-data Cert
-    = High
-    | Medium
-    | Low
-    deriving (Show)
-
--- | A pointer.
-data Ptr t
-    -- | Of #id form.
-    = Local
-        { target    :: t }
-    -- | Of loc#id form.
-    | Global
-        { target    :: t
-        , location  :: t }
-    deriving (Show, Functor)
-
--- | A derivation structure.
-data Deriv t = Deriv
-    { derivType :: t 
-    , derivFrom :: t }
-    deriving (Show, Functor)
-
--- | A paragraph.
-data Para t = Para
-    { paraID    :: t
-    , sentences :: [Sent t] }
-    deriving (Show, Functor)
-
--- | A sentence.
-data Sent t = Sent
-    { sentID    :: t
-    , names     :: [NE t] }
-    deriving (Show, Functor)
-
--- | A segment element in a file. 
-data NE t = NE
-    { neID          :: t
-    , derived       :: Maybe (Deriv t)
-    , neType        :: t
-    , subType       :: Maybe t
-    , orth          :: t
-    -- | Left base or Right when.
-    , base          :: Either t t
-    , cert          :: Cert
-    , certComment   :: Maybe t
-    , ptrs          :: [Ptr t] }
-    deriving (Show)
-
-instance Functor NE where
-    fmap f NE{..} = NE
-        { neID          = f neID
-        , derived       = fmap (fmap f) derived
-        , neType        = f neType
-        , subType       = fmap f subType
-        , orth          = f orth
-        , base          = case base of
-            Left x  -> Left  (f x)
-            Right x -> Right (f x)
-        , cert          = cert
-        , certComment   = fmap f certComment
-        , ptrs          = map (fmap f) ptrs }
-
--- | Make NE forest from a segment list and a list of NEs, both lists
--- corresponding to the same sentence.
-mkForest :: Ord t => [Seg t] -> [NE t] -> T.Forest (Either (NE t) (Seg t))
-mkForest xs ns =
-    mapTrees decode (toForest graph)
-  where
-    -- Position of segment ID
-    pos  = (M.!) $ M.fromList (zip (map segID xs) [0..])
-    -- Segment on the given position
-    word = (M.!) $ M.fromList (zip [0..] xs)
-    -- NE with given ID
-    name = (M.!) $ M.fromList [(neID ne, ne) | ne <- ns]
-
-    graph  = mkGraph (0, length xs - 1)
-        [ ( neID ne
-          , map resolve (ptrs ne) )
-        | ne <- ns ]
-
-    resolve (Local ptr)    = Left ptr
-    resolve (Global ptr _) = Right (pos ptr)
-
-    decode (Left neID) = Left (name neID)
-    decode (Right k)   = Right (word k)
diff --git a/Text/NKJP/Morphosyntax.hs b/Text/NKJP/Morphosyntax.hs
--- a/Text/NKJP/Morphosyntax.hs
+++ b/Text/NKJP/Morphosyntax.hs
@@ -1,26 +1,58 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveFunctor #-}
 
 -- | Parsing the NKJP morphosyntax layer.
 
 module Text.NKJP.Morphosyntax
-( parseMorph
+(
+-- * Data types
+  Para (..)
+, Sent (..)
+, Seg (..)
+, Lex (..)
+
+-- * Parsing
+, parseMorph
 , readMorph
 , readCorpus
-, module Data.NKJP.Morphosyntax
 ) where
 
-import System.FilePath (takeBaseName)
 import Data.Maybe (isJust)
 import qualified Data.Text.Lazy as L
 import qualified Data.Text.Lazy.IO as L
-import qualified Data.Text.Lazy.Encoding as L
-import qualified Data.ByteString.Lazy as BS
-import qualified Codec.Compression.GZip as GZip
-import qualified Codec.Archive.Tar as Tar
 
 import Text.XML.PolySoup
-import Data.NKJP.Morphosyntax
+import qualified Text.NKJP.Tar as Tar
 
+-- | A paragraph.
+data Para t = Para
+    { paraID    :: t
+    , sentences :: [Sent t] }
+    deriving (Show, Functor)
+
+-- | A sentence.
+data Sent t = Sent
+    { sentID    :: t
+    , segments  :: [Seg t] }
+    deriving (Show, Functor)
+
+-- | A segment.
+data Seg t = Seg
+    { segID     :: t 
+    , orth      :: t
+    , nps       :: Bool
+    , lexs      :: [Lex t]
+    , choice    :: (t, t) }
+    deriving (Show, Functor)
+
+-- | A lexciacal entry, potential interpretation of the segment.
+data Lex t = Lex
+    { lexID     :: t
+    , base      :: t
+    , ctag      :: t
+    , msds      :: [(t, t)] }
+    deriving (Show, Functor)
+
 -- | TEI NKJP ann_morphosyntax parser.
 type P a = XmlParser L.Text a
 
@@ -80,23 +112,8 @@
 readMorph :: FilePath -> IO [Para L.Text]
 readMorph morphPath = parseMorph <$> L.readFile morphPath
 
--- | Parse NCP the .tar.gz corpus.
-readCorpus :: FilePath -> IO [(FilePath, [Para L.Text])]
-readCorpus tarPath = do
-    map parseEntry . withBase "ann_morphosyntax" <$> readTar tarPath
-
-readTar :: FilePath -> IO [Tar.Entry]
-readTar tar
-    =  Tar.foldEntries (:) [] (error . show)
-    .  Tar.read . GZip.decompress
-   <$> BS.readFile tar
-
-parseEntry :: Tar.Entry -> (FilePath, [Para L.Text])
-parseEntry entry =
-    (Tar.entryPath entry, parseMorph content)
-  where
-    (Tar.NormalFile binary _) = Tar.entryContent entry
-    content = L.decodeUtf8 binary
-
-withBase :: String -> [Tar.Entry] -> [Tar.Entry]
-withBase baseName = filter ((==baseName) . takeBaseName . Tar.entryPath)
+-- | Parse all ann_morphosyntax.xml files from the NCP .tar.gz file.
+-- Directories will be processed in an ascending order (with
+-- respect to directory names).
+readCorpus :: FilePath -> IO [(FilePath, Maybe [Para L.Text])]
+readCorpus = Tar.readCorpus "ann_morphosyntax" parseMorph
diff --git a/Text/NKJP/Named.hs b/Text/NKJP/Named.hs
--- a/Text/NKJP/Named.hs
+++ b/Text/NKJP/Named.hs
@@ -1,25 +1,130 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE RecordWildCards #-}
 
 -- | Parsing the NKJP named entity layer.
 
 module Text.NKJP.Named
-( parseNamed
+(
+-- * Data types
+  Cert (..)
+, Ptr (..)
+, Deriv (..)
+, Para (..)
+, Sent (..)
+, NE (..)
+
+-- * Parsing
+, parseNamed
 , readNamed
 , readCorpus
-, module Data.NKJP.Named
+, readTrees
+
+-- * Utilities
+, mkForest
 ) where
 
-import System.FilePath (takeBaseName)
+import Data.Maybe (mapMaybe)
+import qualified Data.Map as M
+import qualified Data.Tree as T
 import qualified Data.Text.Lazy as L
 import qualified Data.Text.Lazy.IO as L
-import qualified Data.Text.Lazy.Encoding as L
-import qualified Data.ByteString.Lazy as BS
-import qualified Codec.Compression.GZip as GZip
-import qualified Codec.Archive.Tar as Tar
 
 import Text.XML.PolySoup
-import Data.NKJP.Named
+import qualified Data.Named.Graph as Nd
+import qualified Data.Named.Tree as Nd
+import qualified Text.NKJP.Tar as Tar
+import qualified Text.NKJP.Morphosyntax as Mx
 
+-- | A certainty of an annotator.
+data Cert
+    = High
+    | Medium
+    | Low
+    deriving (Show)
+
+-- | A pointer.
+data Ptr t
+    -- | Of #id form.
+    = Local
+        { target    :: t }
+    -- | Of loc#id form.
+    | Global
+        { target    :: t
+        , location  :: t }
+    deriving (Show, Functor)
+
+-- | A derivation structure.
+data Deriv t = Deriv
+    { derivType :: t 
+    , derivFrom :: t }
+    deriving (Show, Functor)
+
+-- | A paragraph.
+data Para t = Para
+    { paraID    :: t
+    , sentences :: [Sent t] }
+    deriving (Show, Functor)
+
+-- | A sentence.
+data Sent t = Sent
+    { sentID    :: t
+    , names     :: [NE t] }
+    deriving (Show, Functor)
+
+-- | A segment element in a file. 
+data NE t = NE
+    { neID          :: t
+    , derived       :: Maybe (Deriv t)
+    , neType        :: t
+    , subType       :: Maybe t
+    , orth          :: t
+    -- | Left base or Right when.
+    , base          :: Either t t
+    , cert          :: Cert
+    , certComment   :: Maybe t
+    , ptrs          :: [Ptr t] }
+    deriving (Show)
+
+instance Functor NE where
+    fmap f NE{..} = NE
+        { neID          = f neID
+        , derived       = fmap (fmap f) derived
+        , neType        = f neType
+        , subType       = fmap f subType
+        , orth          = f orth
+        , base          = case base of
+            Left x  -> Left  (f x)
+            Right x -> Right (f x)
+        , cert          = cert
+        , certComment   = fmap f certComment
+        , ptrs          = map (fmap f) ptrs }
+
+-- | Make NE forest from a segment list and a list of NEs, both lists
+-- corresponding to the same sentence.
+mkForest :: Ord t => [Mx.Seg t] -> [NE t]
+         -> T.Forest (Either (NE t) (Mx.Seg t))
+mkForest xs ns =
+    Nd.mapForest decode (Nd.toForest graph)
+  where
+    -- Position of segment ID
+    pos  = (M.!) $ M.fromList (zip (map Mx.segID xs) [0..])
+    -- Segment on the given position
+    word = (M.!) $ M.fromList (zip [0..] xs)
+    -- NE with given ID
+    name = (M.!) $ M.fromList [(neID ne, ne) | ne <- ns]
+
+    graph  = Nd.mkGraph (0, length xs - 1)
+        [ ( neID ne
+          , map resolve (ptrs ne) )
+        | ne <- ns ]
+
+    resolve (Local ptr)    = Left ptr
+    resolve (Global ptr _) = Right (pos ptr)
+
+    decode (Left neID) = Left (name neID)
+    decode (Right k)   = Right (word k)
+
 -- | TEI NKJP ann_morphosyntax parser.
 type P a = XmlParser L.Text a
 
@@ -101,23 +206,28 @@
 readNamed :: FilePath -> IO [Para L.Text]
 readNamed namedPath = parseNamed <$> L.readFile namedPath
 
--- | Parse the NCP .tar.gz file.
-readCorpus :: FilePath -> IO [(FilePath, [Para L.Text])]
-readCorpus tarPath = do
-    map parseEntry . withBase "ann_named" <$> readTar tarPath
-
-readTar :: FilePath -> IO [Tar.Entry]
-readTar tar
-    =  Tar.foldEntries (:) [] (error . show)
-    .  Tar.read . GZip.decompress
-   <$> BS.readFile tar
+-- | Parse all ann_named.xml files from the NCP .tar.gz file.
+-- Directories will be processed in an ascending order (with
+-- respect to directory names).
+readCorpus :: FilePath -> IO [(FilePath, Maybe [Para L.Text])]
+readCorpus = Tar.readCorpus "ann_named" parseNamed
 
-parseEntry :: Tar.Entry -> (FilePath, [Para L.Text])
-parseEntry entry =
-    (Tar.entryPath entry, parseNamed content)
+-- | Parse the NCP .tar.gz corpus, extract all NEs and translate them
+-- to the tree form using the 'mkForest' function. 
+readTrees :: FilePath -> IO [T.Forest (Either (NE L.Text) (Mx.Seg L.Text))]
+readTrees path = do
+    morph <- Mx.readCorpus path
+    named <- readCorpus path
+    return . concat $ map toTrees (sync morph named)
   where
-    (Tar.NormalFile binary _) = Tar.entryContent entry
-    content = L.decodeUtf8 binary
+    toTrees (_, xs, ys) = map toForest $ zip
+        (concatMap Mx.sentences xs)
+        (concatMap sentences ys)
+    toForest (x, y) = mkForest (Mx.segments x) (names y)
 
-withBase :: String -> [Tar.Entry] -> [Tar.Entry]
-withBase baseName = filter ((==baseName) . takeBaseName . Tar.entryPath)
+sync :: [(FilePath, Maybe a)] -> [(FilePath, Maybe b)] -> [(FilePath, a, b)]
+sync as bs =
+    mapMaybe (uncurry just) (zip as bs)
+  where
+    just (dir, Just x) (_, Just y) = Just (dir, x, y)
+    just _ _ = Nothing
diff --git a/Text/NKJP/Tar.hs b/Text/NKJP/Tar.hs
new file mode 100644
--- /dev/null
+++ b/Text/NKJP/Tar.hs
@@ -0,0 +1,40 @@
+module Text.NKJP.Tar
+( readCorpus
+) where
+
+import Control.Applicative ((<$>), (<*>))
+import System.FilePath (takeBaseName, takeDirectory)
+import Data.List (groupBy, find)
+import Data.Function (on)
+import qualified Data.Text.Lazy as L
+import qualified Data.Text.Lazy.Encoding as L
+import qualified Codec.Compression.GZip as GZip
+import qualified Codec.Archive.Tar as Tar
+import qualified Data.ByteString.Lazy as BS
+
+readTar :: FilePath -> IO [Tar.Entry]
+readTar tar
+    =  Tar.foldEntries (:) [] (error . show)
+    .  Tar.read . GZip.decompress
+   <$> BS.readFile tar
+
+withBase :: String -> [Tar.Entry] -> Maybe Tar.Entry
+withBase baseName = find ((==baseName) . takeBaseName . Tar.entryPath)
+
+procContent :: (L.Text -> a) -> Tar.Entry -> a
+procContent f entry =
+    let (Tar.NormalFile binary _) = Tar.entryContent entry
+    in  f (L.decodeUtf8 binary)
+
+-- | Visit each .tar directory and return (apart from the directory name)
+-- processed contents of the entry with the given name or Nothing if such
+-- entry doesn't exists in the directory.
+readCorpus :: String -> (L.Text -> a) -> FilePath -> IO [(FilePath, Maybe a)]
+readCorpus base f tarPath
+    = map onGroup
+    . groupBy ((==) `on` (takeDirectory . Tar.entryPath))
+   <$> readTar tarPath
+  where
+    onGroup = (,)
+        <$> takeDirectory . Tar.entryPath . head
+        <*> (fmap (procContent f) . withBase base)
diff --git a/nkjp.cabal b/nkjp.cabal
--- a/nkjp.cabal
+++ b/nkjp.cabal
@@ -1,5 +1,5 @@
 name:               nkjp
-version:            0.1.1
+version:            0.2.0
 synopsis:           Manipulating the National Corpus of Polish (NKJP)
 description:
     The library provides parsing and printing utilities for the
@@ -25,16 +25,22 @@
       , zlib
       , tar >= 0.4 && < 0.5
       , filepath
-      , data-named
+      , data-named >= 0.4 && < 0.5
 
     exposed-modules:
-        Data.NKJP.Morphosyntax
-      , Data.NKJP.Named
-      , Text.NKJP.Morphosyntax
+        Text.NKJP.Morphosyntax
       , Text.NKJP.Named
 
+    other-modules:
+        Text.NKJP.Tar
+
     ghc-options: -Wall
 
 source-repository head
     type: git
     location: git://github.com/kawu/nkjp.git
+
+executable named2enamex
+  hs-source-dirs: ., tools
+  main-is: named2enamex.hs
+  ghc-options: -Wall -O2
diff --git a/tools/named2enamex.hs b/tools/named2enamex.hs
new file mode 100644
--- /dev/null
+++ b/tools/named2enamex.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Control.Monad (forM_, (>=>))
+import System.Environment (getArgs)
+import Data.Maybe (catMaybes)
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as L
+import qualified Data.Text.Lazy.IO as L
+
+import Data.Named.Tree
+import Text.Named.Enamex (showForest)
+import qualified Text.NKJP.Named as Ne
+import qualified Text.NKJP.Morphosyntax as Mx
+
+neType :: Ne.NE L.Text -> T.Text
+neType ne = L.toStrict . L.intercalate "." . catMaybes . map ($ ne) $
+    [ Just . Ne.neType
+    , Ne.subType
+    , Ne.derived >=> return . Ne.derivType ]
+
+orth :: Mx.Seg L.Text -> T.Text
+orth = L.toStrict . Mx.orth
+
+main :: IO ()
+main = do
+    [teiPath] <- getArgs
+    fs <- Ne.readTrees teiPath
+    forM_ fs $ \ts ->
+        L.putStrLn . showForest . mapForest (onEither neType orth) $ ts
