{- | Export table data as OpenDocument Spreadsheet . This format supports character encodings, fixed header rows and columns, number formatting, text styles, merged cells, formulas, hyperlinks. Currently we support Flat ODS, a plain uncompressed XML format. This is derived from -} module Hledger.Write.Ods where import Hledger.Data.Types (CommoditySymbol, Amount, AmountPrecision(..)) import Hledger.Data.Types (acommodity, aquantity, astyle, asprecision) import qualified Data.Text.Lazy as TL import qualified Data.Text as T import Data.Text (Text) import qualified Data.Map as Map import qualified Data.Set as Set import Data.Foldable (fold) import Data.Map (Map) import Data.Set (Set) import Data.Maybe (mapMaybe) import qualified System.IO as IO import Text.Printf (printf) data Type = TypeString | TypeAmount !Amount | TypeMixedAmount deriving (Eq, Ord, Show) data Style = Ordinary | Head | Foot deriving (Eq, Ord, Show) data Cell = Cell { cellType :: Type, cellStyle :: Style, cellContent :: Text } defaultCell :: Cell defaultCell = Cell { cellType = TypeString, cellStyle = Ordinary, cellContent = T.empty } printFods :: IO.TextEncoding -> Map Text ((Maybe Int, Maybe Int), [[Cell]]) -> TL.Text printFods encoding tables = let fileOpen customStyles = map (map (\c -> case c of '\'' -> '"'; _ -> c)) $ printf "" (show encoding) : "" : "" : " " : " " : " " : " " : " " : " " : " " : " " : " " : " " : " " : " " : " " : " " : customStyles ++ "" : [] fileClose = "" : [] tableConfig tableNames = " " : " " : " " : " " : " " : (fold $ flip Map.mapWithKey tableNames $ \tableName (mTopRow,mLeftColumn) -> printf " " tableName : (flip foldMap mLeftColumn $ \leftColumn -> " 2" : printf " %d" leftColumn : printf " %d" leftColumn : []) ++ (flip foldMap mTopRow $ \topRow -> " 2" : printf " %d" topRow : printf " %d" topRow : []) ++ " " : []) ++ " " : " " : " " : " " : " " : [] tableOpen name = "" : "" : printf "" name : [] tableClose = "" : "" : "" : [] in TL.unlines $ map (TL.fromStrict . T.pack) $ fileOpen (numberConfig =<< Set.toList (numberStyles (foldMap (concat.snd) tables))) ++ tableConfig (fmap fst tables) ++ (Map.toAscList tables >>= \(name,(_,table)) -> tableOpen name ++ (table >>= \row -> "" : (row >>= formatCell) ++ "" : []) ++ tableClose) ++ fileClose numberStyles :: [Cell] -> Set (CommoditySymbol, AmountPrecision) numberStyles = Set.fromList . mapMaybe (\cell -> case cellType cell of TypeAmount amt -> Just (acommodity amt, asprecision $ astyle amt) _ -> Nothing) numberStyleName :: (CommoditySymbol, AmountPrecision) -> String numberStyleName (comm, prec) = printf "%s-%s" comm $ case prec of NaturalPrecision -> "natural" Precision k -> show k numberConfig :: (CommoditySymbol, AmountPrecision) -> [String] numberConfig (comm, prec) = let precStr = case prec of NaturalPrecision -> "" Precision k -> printf " number:decimal-places='%d'" k name = numberStyleName (comm, prec) in printf " " name : printf " " precStr : printf " %s%s" (if T.null comm then "" else " ") comm : " " : " " name name : [] formatCell :: Cell -> [String] formatCell cell = let style, valueType :: String style = case (cellStyle cell, cellType cell) of (Ordinary, TypeString) -> "" (Ordinary, TypeMixedAmount) -> " table:style-name='amount'" (Ordinary, TypeAmount amt) -> numberStyle amt (Foot, TypeString) -> " table:style-name='foot'" (Foot, _) -> " table:style-name='total-amount'" (Head, _) -> " table:style-name='head'" numberStyle amt = printf " table:style-name='%s'" (numberStyleName (acommodity amt, asprecision $ astyle amt)) valueType = case cellType cell of TypeAmount amt -> printf "office:value-type='float' office:value='%s'" (show $ aquantity amt) _ -> "office:value-type='string'" in printf "" style valueType : printf "%s" (cellContent cell) : "" : []