diff --git a/ods2csv.cabal b/ods2csv.cabal
--- a/ods2csv.cabal
+++ b/ods2csv.cabal
@@ -1,5 +1,5 @@
 Name:                ods2csv
-Version:             0.0
+Version:             0.1
 Synopsis:            Convert Open Document Spreadsheet ODS to CSV
 Description:
   Usually you would convert ODS to CSV via @libreoffice --headless@
@@ -8,13 +8,32 @@
   and has incomprehensible CSV export command-line options
   and selection of individual tables is inferior.
   .
-  This program quickly scans through a FODS document using a XML parser
+  This program quickly scans through a FODS or ODS document using a XML parser
   and watches only the necessary data.
   It does not interpret or evaluate the formula data,
   instead it assumes that the contained evaluated values are correct.
   This is true, if the file was saved from LibreOffice
   but might not be true if generated or manipulated by other tools.
   .
+  You can feed generated CSV files to LaTeX @datatool.sty@, @csvmidi@,
+  or @csvreplace@ from Haskell package @spreadsheet@.
+  .
+  Example runs of the program:
+  .
+  > ods2csv --list-tables input.ods
+  .
+  > ods2csv --sheetnumber=1 input.ods >output.csv
+  .
+  > ods2csv --sheetname="Table 2" --separator=TAB input.ods >output.tsv
+  .
+  > ods2csv --sheetnumber=2 input.fods >output.csv
+  .
+  > ods2csv --sheetnumber=2 <input.fods >output.csv
+  .
+  The program supports UTF-8 encoding for FODS/XML input,
+  and applies Latin1 decoding otherwise.
+  For writing CSV it uses the system's default encoding.
+  .
   ToDo: Provide FODS parser as library function, maybe in @spreadsheet@.
 Homepage:            https://hub.darcs.net/thielema/ods2csv
 License:             BSD3
@@ -26,7 +45,7 @@
 Cabal-Version:       >=1.10
 
 Source-Repository this
-  Tag:         0.0
+  Tag:         0.1
   Type:        darcs
   Location:    https://hub.darcs.net/thielema/ods2csv
 
@@ -36,11 +55,14 @@
 
 Executable ods2csv
   Build-Depends:
+    zip-archive >=0.4.3 && <0.5,
     spreadsheet >=0.1.3 && <0.2,
     tagchup >=0.4 && <0.5,
     xml-basic >=0.1.1 && <0.2,
     shell-utility >=0.1 && <0.2,
     optparse-applicative >=0.11 && <0.19,
+    utf8-string >=1.0.2 && <1.1,
+    bytestring >=0.10 && <0.13,
     non-empty >=0.3.4 && <0.4,
     utility-ht >=0.0.10 && <0.1,
     base >=4.5 && <5
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -6,16 +6,24 @@
 import qualified Options.Applicative as OP
 
 import qualified Text.HTML.Tagchup.Parser as TagParser
+import qualified Text.HTML.Tagchup.Process as TagProc
 import qualified Text.HTML.Tagchup.Tag.Match as TagMatch
 import qualified Text.HTML.Tagchup.Tag as Tag
 import qualified Text.XML.Basic.Attribute as Attr
-import qualified Text.XML.Basic.Name.MixedCase as Name
+import qualified Text.XML.Basic.Name.MixedCase as Name -- Qualified would also be appropriate
 import qualified Text.XML.Basic.Name as NameC
 
+import qualified Codec.Archive.Zip as Zip
+import qualified Data.ByteString.Lazy.UTF8 as B_UTF8
+import qualified Data.ByteString.Lazy.Char8 as BC
+
 import qualified Data.Spreadsheet as Spreadsheet
 import qualified Data.NonEmpty.Mixed as NonEmptyM
 import qualified Data.NonEmpty as NonEmpty
 import qualified Data.List.HT as ListHT
+import qualified Data.List as List
+import qualified Data.Char as Char
+import Data.Function.HT (Id)
 import Data.Tuple.HT (mapSnd)
 import Data.Maybe (mapMaybe)
 import Data.Monoid ((<>))
@@ -27,6 +35,35 @@
 
 
 
+readByteContent :: FilePath -> IO BC.ByteString
+readByteContent path =
+   if List.isSuffixOf ".ods" path
+      then do
+         archive <- Zip.toArchive <$> BC.readFile path
+         case filter (("content.xml"==) . Zip.eRelativePath) $
+               Zip.zEntries archive of
+            [] -> exitFailureMsg "content.xml missing in ODS file"
+            -- FixMe: Zip.fromEntry is a partial function
+            contentEntry : _ -> return $ Zip.fromEntry contentEntry
+      else BC.readFile path
+
+
+fixNameType :: Id [Tag.T Name.T String]
+fixNameType = id
+
+{-
+In principle it would be fine to ignore encoding, that is,
+read and write bytestrings and thus transfer encoding from XML to CSV.
+However, we need to match table names.
+-}
+decode :: BC.ByteString -> String
+decode bytes =
+   case fmap (map Char.toLower) $ TagProc.getXMLEncoding $
+         fixNameType $ TagParser.runSoup $ BC.take 10000 bytes of
+      Just "utf-8" -> B_UTF8.toString bytes
+      _ -> BC.unpack bytes
+
+
 maybeTableName :: Tag.T Name.T String -> Maybe String
 maybeTableName tag = do
    (foundName, attrs) <- Tag.maybeOpen tag
@@ -48,9 +85,10 @@
 extractTablesNames :: [Tag.T Name.T String] -> [String]
 extractTablesNames = mapMaybe maybeTableName
 
-listTables :: FilePath -> IO ()
-listTables input =
-   mapM_ putStrLn . extractTablesNames . TagParser.runSoup =<< readFile input
+listTables :: IO BC.ByteString -> IO ()
+listTables readInput =
+   mapM_ putStrLn . extractTablesNames . TagParser.runSoup . decode
+      =<< readInput
 
 
 extractTablesContents :: [Tag.T Name.T String] -> [(String, [[String]])]
@@ -81,20 +119,20 @@
             (TagMatch.openNameLit "table:table-row"))) .
    snd . ListHT.segmentBeforeJust maybeTableName
 
-contentFromTables :: Char -> FilePath -> IO ()
-contentFromTables separator input =
+contentFromTables :: Char -> IO BC.ByteString -> IO ()
+contentFromTables separator readInput =
    mapM_
       (\(tableName, content) -> do
          putStrLn ""
          putStrLn tableName
          putStrLn (Spreadsheet.toString '"' separator content)) .
    extractTablesContents .
-   TagParser.runSoup
-      =<< readFile input
+   TagParser.runSoup . decode
+      =<< readInput
 
-contentFromTable :: Char -> Either String Int -> FilePath -> IO ()
-contentFromTable separator tableSelector input = do
-   tables <- extractTablesContents . TagParser.runSoup <$> readFile input
+contentFromTable :: Char -> Either String Int -> IO BC.ByteString -> IO ()
+contentFromTable separator tableSelector readInput = do
+   tables <- extractTablesContents . TagParser.runSoup . decode <$> readInput
    putStr . Spreadsheet.toString '"' separator =<<
       case filter (\(tableId, (tableName, _content)) ->
                      either (tableName==) (tableId==) tableSelector) $
@@ -103,7 +141,7 @@
          _ ->
             exitFailureMsg $
                "table with " ++
-               either (printf "number %d") (printf "name %s") tableSelector ++
+               either (printf "name %s") (printf "number %d") tableSelector ++
                " not found"
 
 
@@ -140,8 +178,10 @@
                OP.help "Select table by number")
          )))
    <*>
-      OP.strArgument
+      OP.argument
+         (OP.maybeReader $ Just . readByteContent)
          (OP.metavar "INPUT" <>
+          OP.value BC.getContents <>
           OP.help "Input Document")
 
 info :: OP.Parser a -> OP.ParserInfo a
