diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+## Example: `csvreplace`
+
+If you build the package with the Cabal flag `-fbuildExamples`
+then the program `csvreplace` will be built.
+It allows you to replace placeholders in a template file
+according to the columns of a CSV file.
+E.g. given a file `template.txt` with content
+
+~~~~
+Name: FIRSTNAME SURNAME
+Born: BIRTH
+~~~~
+
+and `names.csv` with content
+
+~~~~
+"FIRSTNAME","SURNAME",BIRTH
+"Georg","Cantor",1845
+"Haskell","Curry",1900
+"Ada","Lovelace",1815
+~~~~
+
+the call
+
+~~~~
+csvreplace template.txt <names.csv
+~~~~
+
+produces the output
+
+~~~~
+Name: Georg Cantor
+Born: 1845
+Name: Haskell Curry
+Born: 1900
+Name: Ada Lovelace
+Born: 1815
+~~~~
+
+You may also generate one file per CSV row in the following manner:
+
+~~~~
+csvreplace --multifile=FIRSTNAME-SURNAME.txt template.txt <names.csv
+~~~~
+
+
+### Character Encoding
+
+For simple replacement of parts of the text
+we would not need to decode the input texts
+and thus we would not need to know the used encoding scheme.
+Essentially, we would only require that both CSV and template file
+employ the same character encoding.
+
+However, it is not as simple as that.
+We need to decode the structure of the CSV file.
+In multi-file mode we also need to generate proper file names.
+Both requirements force us to decode both CSV and template file.
+For the de- and encoding we use the default locale encoding.
+
+If you want essentially a byte-by-byte replacement
+and you assert that all files are in the same encoding
+where the commas and quotation marks are compatible with ASCII
+then you can set the encoding locally
+to a complete 8-bit encoding like `latin1` as in:
+
+~~~~
+LANG=de_DE csvreplace --multifile=FIRSTNAME-SURNAME.txt template.txt <names.csv
+~~~~
diff --git a/example/CSVReplace.hs b/example/CSVReplace.hs
new file mode 100644
--- /dev/null
+++ b/example/CSVReplace.hs
@@ -0,0 +1,101 @@
+module Main where
+
+import qualified Data.Spreadsheet as Sheet
+import qualified Data.List.HT as ListHT
+
+import System.Console.GetOpt (getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..), usageInfo, )
+import System.Environment (getArgs, getProgName, )
+import qualified System.Exit as Exit
+import qualified System.IO as IO
+
+import qualified Control.Monad.Exception.Asynchronous as AExc
+import qualified Control.Monad.Exception.Synchronous as Exc
+import Control.Monad.Trans.Class (lift, )
+import Control.Monad (when, )
+import Control.Applicative ((<$>), )
+
+import qualified Data.Foldable as Fold
+import Data.Foldable (forM_, )
+
+
+data Flags =
+   Flags {
+      optMultiFile :: Maybe FilePath,
+      optQuotation,
+      optDelimiter :: Char
+   }
+
+defltFlags :: Flags
+defltFlags = Flags {
+      optMultiFile = Nothing,
+      optQuotation = '"',
+      optDelimiter = ','
+   }
+
+exitFailureMsg :: String -> IO a
+exitFailureMsg msg = do
+   IO.hPutStrLn IO.stderr msg
+   Exit.exitFailure
+
+options :: [OptDescr (Flags -> IO Flags)]
+options =
+   Option ['h'] ["help"]
+      (NoArg (\_ -> do
+         programName <- getProgName
+         putStrLn $ flip usageInfo options $
+            "Usage: " ++ programName ++ " [OPTIONS] TEMPLATE-FILE\n" ++
+            "The CSV file is read from standard input.\n"
+         Exit.exitSuccess))
+      "show options" :
+   Option [] ["multifile"]
+      (flip ReqArg "FILEPATTERN" $ \str flags ->
+         return $ flags{optMultiFile = Just str})
+      "generate one file per CSV row" :
+   Option ['d'] ["delimiter"]
+      (flip ReqArg "CHAR" $ \str flags -> do
+         case str of
+            [c] -> return $ flags{optDelimiter = c}
+            _ -> exitFailureMsg $ "delimiter must be one character, which " ++ show str ++ " is not")
+      "field delimiter character" :
+   Option ['q'] ["quotation"]
+      (flip ReqArg "CHAR" $ \str flags -> do
+         case str of
+            [c] -> return $ flags{optQuotation = c}
+            _ -> exitFailureMsg $ "quotation mark must be one character, which " ++ show str ++ " is not")
+      "quotation mark character" :
+   []
+
+
+replaceRow :: String -> [String] -> [String] -> String
+replaceRow template names row =
+   ListHT.multiReplace (filter (not . null . fst) $ zip names row) template
+
+replace :: String -> [String] -> Sheet.T -> String
+replace template names = concatMap (replaceRow template names)
+
+
+main :: IO ()
+main =
+   Exc.resolveT (\e -> exitFailureMsg $ "Aborted: " ++ e) $ do
+      argv <- lift getArgs
+      let (opts, files, errors) = getOpt RequireOrder options argv
+      when (not (null errors)) $ Exc.throwT $ concat $ errors
+      flags <- lift $ foldr (=<<) (return defltFlags) opts
+      case files of
+         [templateName] -> do
+            template <- lift $ readFile templateName
+            sheet <-
+               Sheet.fromString (optQuotation flags) (optDelimiter flags) <$>
+               lift getContents
+            case AExc.result sheet of
+               [] -> Exc.throwT "empty CSV input"
+               names:rows ->
+                  lift $
+                  case optMultiFile flags of
+                     Nothing -> putStr $ replace template names rows
+                     Just filePattern ->
+                        forM_ rows $ \row ->
+                           writeFile (replaceRow filePattern names row) $
+                              replaceRow template names row
+            Fold.mapM_ Exc.throwT $ AExc.exception sheet
+         _ -> Exc.throwT "I need exactly one template file."
diff --git a/spreadsheet.cabal b/spreadsheet.cabal
--- a/spreadsheet.cabal
+++ b/spreadsheet.cabal
@@ -1,5 +1,5 @@
 Name:             spreadsheet
-Version:          0.1.3.4
+Version:          0.1.3.5
 License:          BSD3
 License-File:     LICENSE
 Author:           Henning Thielemann <haskell@henning-thielemann.de>
@@ -10,58 +10,39 @@
 Description:
   Read and write spreadsheets from and to files
   containing comma separated values (CSV) in a lazy way.
-  See also the
-     csv package <http://hackage.haskell.org/package/csv> and
-     <http://www.xoltar.org/languages/haskell.html>,
-     <http://www.xoltar.org/languages/haskell/CSV.hs>.
-  Both do not parse lazy.
   Reading from other source than plain 'String's could be easily added.
   .
   If you install this package by
   .
-  cabal install -fbuildExamples
+  > cabal install -fbuildExamples
   .
-  then an example program is compiled and installed, too.
+  then the example program @csvreplace@ is compiled and installed, too.
   This program fills a template text using data from a CSV file.
-  E.g. given a file @template.txt@ with content
-  .
-  > Name: FIRSTNAME SURNAME
-  > Born: BIRTH
-  .
-  and @names.csv@ with content
-  .
-  > "FIRSTNAME","SURNAME",BIRTH
-  > "Georg","Cantor",1845
-  > "Haskell","Curry",1900
-  > "Ada","Lovelace",1815
-  .
-  the call
-  .
-  > csvreplace template.txt <names.csv
+  For similar (non-Haskell) programs see @cut@, @csvfix@, @csvtool@.
   .
-  produces the output
+  Related packages:
   .
-  > Name: Georg Cantor
-  > Born: 1845
-  > Name: Haskell Curry
-  > Born: 1900
-  > Name: Ada Lovelace
-  > Born: 1815
+  * @csv@: strict parser
   .
-  You may also generate one file per CSV row in the following manner:
+  * <http://www.xoltar.org/languages/haskell.html>,
+    <http://www.xoltar.org/languages/haskell/CSV.hs>: strict parser
   .
-  > csvreplace --multifile=FIRSTNAME-SURNAME.txt template.txt <names.csv
+  * @lazy-csv@: lazy @String@ and @ByteString@ parser
   .
-  For similar (non-Haskell) programs see @cut@, @csvfix@, @csvtool@.
-Tested-With:       GHC==6.8.2, GHC==6.12.3
-Cabal-Version:     >=1.6
+  * @cassava@: high-level CSV parser that treats rows as records,
+    parses ByteStrings and is biased towards UTF-8 encoding.
+Tested-With:       GHC==7.4.2, GHC==7.8.4
+Cabal-Version:     >=1.8
 Build-Type:        Simple
+Extra-Source-Files:
+  README.md
+
 Source-Repository head
   Type:     darcs
   Location: http://code.haskell.org/~thielema/spreadsheet/
 
 Source-Repository this
-  Tag:      0.1.3.4
+  Tag:      0.1.3.5
   Type:     darcs
   Location: http://code.haskell.org/~thielema/spreadsheet/
 
@@ -93,9 +74,12 @@
 Executable csvreplace
   If flag(buildExamples)
     Build-Depends:
-      bytestring >=0.9 && <0.11
+      spreadsheet,
+      utility-ht,
+      transformers,
+      explicit-exception,
+      base
   Else
     Buildable:      False
   GHC-Options:      -Wall
-  Hs-Source-Dirs:   src
-  Main-Is: CSVReplace.hs
+  Main-Is: example/CSVReplace.hs
diff --git a/src/CSVReplace.hs b/src/CSVReplace.hs
deleted file mode 100644
--- a/src/CSVReplace.hs
+++ /dev/null
@@ -1,107 +0,0 @@
-module Main where
-
-import qualified Data.Spreadsheet as Sheet
-import qualified Data.List.HT as ListHT
-
-import System.Console.GetOpt (getOpt, ArgOrder(..), OptDescr(..), ArgDescr(..), usageInfo, )
-import System.Environment (getArgs, getProgName, )
-import qualified System.Exit as Exit
-import qualified System.IO as IO
-
-import qualified Data.ByteString.Char8 as B
-import qualified Data.ByteString.Lazy.Char8 as BL
-
-import qualified Control.Monad.Exception.Asynchronous as AExc
-import qualified Control.Monad.Exception.Synchronous as Exc
-import Control.Monad.Trans.Class (lift, )
-import Control.Monad (when, )
-import Data.Foldable (forM_, )
-
-
-data Flags =
-   Flags {
-      optMultiFile :: Maybe FilePath,
-      optQuotation,
-      optDelimiter :: Char
-   }
-
-defltFlags :: Flags
-defltFlags = Flags {
-      optMultiFile = Nothing,
-      optQuotation = '"',
-      optDelimiter = ','
-   }
-
-exitFailureMsg :: String -> IO a
-exitFailureMsg msg = do
-   IO.hPutStrLn IO.stderr msg
-   Exit.exitFailure
-
-options :: [OptDescr (Flags -> IO Flags)]
-options =
-   Option ['h'] ["help"]
-      (NoArg (\_ -> do
-         programName <- getProgName
-         putStrLn $ flip usageInfo options $
-            "Usage: " ++ programName ++ " [OPTIONS] TEMPLATE-FILE\n" ++
-            "The CSV file is read from standard input.\n"
-         Exit.exitSuccess))
-      "show options" :
-   Option [] ["multifile"]
-      (flip ReqArg "FILEPATTERN" $ \str flags ->
-         return $ flags{optMultiFile = Just str})
-      "generate one file per CSV row" :
-   Option ['d'] ["delimiter"]
-      (flip ReqArg "CHAR" $ \str flags -> do
-         case str of
-            [c] -> return $ flags{optDelimiter = c}
-            _ -> exitFailureMsg $ "delimiter must be one character, which " ++ show str ++ " is not")
-      "field delimiter character" :
-   Option ['q'] ["quotation"]
-      (flip ReqArg "CHAR" $ \str flags -> do
-         case str of
-            [c] -> return $ flags{optQuotation = c}
-            _ -> exitFailureMsg $ "quotation mark must be one character, which " ++ show str ++ " is not")
-      "quotation mark character" :
-   []
-
-
-replaceRow :: String -> [String] -> [String] -> String
-replaceRow template names row =
-   ListHT.multiReplace (filter (not . null . fst) $ zip names row) template
-
-replaceRowBS :: String -> [String] -> [String] -> BL.ByteString
-replaceRowBS template names row =
-   BL.pack $ replaceRow template names row
-
-replace :: String -> [String] -> Sheet.T -> BL.ByteString
-replace template names =
-   BL.concat . map (replaceRowBS template names)
-
-
-main :: IO ()
-main =
-   Exc.resolveT (\e -> exitFailureMsg $ "Aborted: " ++ e) $ do
-      argv <- lift getArgs
-      let (opts, files, errors) = getOpt RequireOrder options argv
-      when (not (null errors)) $ Exc.throwT $ concat $ errors
-      flags <- lift $ foldr (=<<) (return defltFlags) opts
-      case files of
-         [templateName] -> do
-            template <- fmap B.unpack $ lift $ B.readFile templateName
-            sheet <-
-               fmap (Sheet.fromString (optQuotation flags) (optDelimiter flags)
-                      . BL.unpack) $
-               lift BL.getContents
-            case AExc.result sheet of
-               [] -> Exc.throwT "empty CSV input"
-               (names:rows) ->
-                  case optMultiFile flags of
-                     Nothing -> do
-                        lift $ BL.putStr $ replace template names rows
-                        forM_ (AExc.exception sheet) $ Exc.throwT
-                     Just filePattern ->
-                        lift $ forM_ rows $ \row ->
-                           BL.writeFile (replaceRow filePattern names row) $
-                              replaceRowBS template names row
-         _ -> Exc.throwT "I need exactly one template file."
