packages feed

ods2csv (empty) → 0.0

raw patch · 4 files changed

+239/−0 lines, 4 filesdep +basedep +non-emptydep +optparse-applicativesetup-changed

Dependencies added: base, non-empty, optparse-applicative, shell-utility, spreadsheet, tagchup, utility-ht, xml-basic

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2023, Henning Thielemann++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.++    * Neither the name of Henning Thielemann nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++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.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#! /usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ ods2csv.cabal view
@@ -0,0 +1,51 @@+Name:                ods2csv+Version:             0.0+Synopsis:            Convert Open Document Spreadsheet ODS to CSV+Description:+  Usually you would convert ODS to CSV via @libreoffice --headless@+  but this conflicts with running GUI instances of @libreoffice@+  and requires a full libreoffice installation+  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+  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.+  .+  ToDo: Provide FODS parser as library function, maybe in @spreadsheet@.+Homepage:            https://hub.darcs.net/thielema/ods2csv+License:             BSD3+License-File:        LICENSE+Author:              Henning Thielemann+Maintainer:          haskell@henning-thielemann.de+Category:            Text, CSV+Build-Type:          Simple+Cabal-Version:       >=1.10++Source-Repository this+  Tag:         0.0+  Type:        darcs+  Location:    https://hub.darcs.net/thielema/ods2csv++Source-Repository head+  Type:        darcs+  Location:    https://hub.darcs.net/thielema/ods2csv++Executable ods2csv+  Build-Depends:+    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,+    non-empty >=0.3.4 && <0.4,+    utility-ht >=0.0.10 && <0.1,+    base >=4.5 && <5+  Hs-Source-Dirs:      src+  Main-Is:             Main.hs+  Default-Language:    Haskell2010+  GHC-Options:+    -Wall -fwarn-incomplete-uni-patterns -fwarn-tabs
+ src/Main.hs view
@@ -0,0 +1,155 @@+module Main where++import Shell.Utility.ParseArgument (parseNumber)+import Shell.Utility.Exit (exitFailureMsg)++import qualified Options.Applicative as OP++import qualified Text.HTML.Tagchup.Parser as TagParser+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 as NameC++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 Data.Tuple.HT (mapSnd)+import Data.Maybe (mapMaybe)+import Data.Monoid ((<>))++import Control.Monad (join, guard)+import Control.Applicative (pure, (<*>), (<|>))++import Text.Printf (printf)++++maybeTableName :: Tag.T Name.T String -> Maybe String+maybeTableName tag = do+   (foundName, attrs) <- Tag.maybeOpen tag+   guard $ NameC.match "table:table" foundName+   Attr.lookupLit "table:name" attrs++maybeCell :: Tag.T Name.T String -> Maybe Int+maybeCell tag = do+   (foundName, attrs) <- Tag.maybeOpen tag+   guard $ NameC.match "table:table-cell" foundName+   case fmap reads $ Attr.lookupLit "table:number-columns-repeated" attrs of+      Just [(n,_)] -> return n+      _ -> return 1++{-+<config:config-item-map-entry config:name="BlaBlaBla">+<table:table table:name="BlaBlaBla" table:style-name="ta1">+-}+extractTablesNames :: [Tag.T Name.T String] -> [String]+extractTablesNames = mapMaybe maybeTableName++listTables :: FilePath -> IO ()+listTables input =+   mapM_ putStrLn . extractTablesNames . TagParser.runSoup =<< readFile input+++extractTablesContents :: [Tag.T Name.T String] -> [(String, [[String]])]+extractTablesContents =+   map (mapSnd (+         map+            (concatMap+               (uncurry $ \n ->+                  (\texts ->+                     replicate n $+                     case texts of+                        text:_ -> text+                        [] -> "")+                   .+                   mapMaybe Tag.maybeText+                   .+                   dropWhile (not . TagMatch.openNameLit "text:p"))+             .+             snd+             .+             ListHT.segmentBeforeJust maybeCell+             .+             NonEmpty.tail)+         .+         snd+         .+         NonEmptyM.segmentBefore+            (TagMatch.openNameLit "table:table-row"))) .+   snd . ListHT.segmentBeforeJust maybeTableName++contentFromTables :: Char -> FilePath -> IO ()+contentFromTables separator input =+   mapM_+      (\(tableName, content) -> do+         putStrLn ""+         putStrLn tableName+         putStrLn (Spreadsheet.toString '"' separator content)) .+   extractTablesContents .+   TagParser.runSoup+      =<< readFile input++contentFromTable :: Char -> Either String Int -> FilePath -> IO ()+contentFromTable separator tableSelector input = do+   tables <- extractTablesContents . TagParser.runSoup <$> readFile input+   putStr . Spreadsheet.toString '"' separator =<<+      case filter (\(tableId, (tableName, _content)) ->+                     either (tableName==) (tableId==) tableSelector) $+           zip [1..] tables of+         (_,(_,found)):_ -> return found+         _ ->+            exitFailureMsg $+               "table with " +++               either (printf "number %d") (printf "name %s") tableSelector +++               " not found"+++parser :: OP.Parser (IO ())+parser =+   ((OP.flag' listTables $+         OP.long "list-tables" <>+         OP.help "List all tables in an ODS document")+      <|>+      (pure contentFromTable+         <*>+         (OP.option+            (OP.eitherReader (\str ->+               case str of+                  "TAB" -> Right '\t'+                  [c] -> Right c+                  _ -> Left "separator must be one character")) $+            OP.long "separator" <>+            OP.metavar "CHAR" <>+            OP.value ',' <>+            OP.help "CSV separator, TAB for tabulator")+         <*>+         (+            (fmap Left $+             OP.strOption $+               OP.long "sheetname" <>+               OP.metavar "NAME" <>+               OP.help "Select table by name")+            <|>+            (fmap Right $+             OP.option (OP.eitherReader $ parseNumber "page" (0<) "positive") $+               OP.long "sheetnumber" <>+               OP.metavar "ONEBASED" <>+               OP.help "Select table by number")+         )))+   <*>+      OP.strArgument+         (OP.metavar "INPUT" <>+          OP.help "Input Document")++info :: OP.Parser a -> OP.ParserInfo a+info p =+   OP.info+      (OP.helper <*> p)+      (OP.fullDesc <>+       OP.progDesc "Convert Open Document Spreadsheet ODS to CSV.")++main :: IO ()+main = join $ OP.execParser $ info parser