packages feed

ods2csv 0.0 → 0.1

raw patch · 2 files changed

+78/−16 lines, 2 filesdep +bytestringdep +utf8-stringdep +zip-archive

Dependencies added: bytestring, utf8-string, zip-archive

Files

ods2csv.cabal view
@@ -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
src/Main.hs view
@@ -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