packages feed

ampersand-3.1.0: src/Database/Design/Ampersand/Output/Population2Xlsx.hs

{-# LANGUAGE OverloadedStrings #-}
module Database.Design.Ampersand.Output.Population2Xlsx
  (fSpec2PopulationXlsx)
where
import Database.Design.Ampersand.FSpec
import Database.Design.Ampersand.Basics
import Database.Design.Ampersand.Core.AbstractSyntaxTree
import System.Time
import qualified Data.Map as M
import Codec.Xlsx
import qualified Data.ByteString.Lazy as L
import qualified Data.Text as T
import Data.Maybe
import Data.List
import Data.Time.Calendar

fatal :: Int -> String -> a
fatal = fatalMsg "Population2Xlsx"

fSpec2PopulationXlsx :: ClockTime -> FSpec -> L.ByteString 
fSpec2PopulationXlsx ct fSpec = 
  fromXlsx ct xlsx
    where
      xlsx =def { _xlSheets = plugs2Sheets fSpec}
               
     

plugs2Sheets :: FSpec -> M.Map T.Text Worksheet
plugs2Sheets fSpec = M.fromList . catMaybes . Prelude.map plug2sheet $ plugInfos fSpec
  where
    plug2sheet :: PlugInfo -> Maybe (T.Text, Worksheet)
    plug2sheet ExternalPlug{} = Nothing  -- Not supported at present
    plug2sheet (InternalPlug plug) = fmap (\x -> (T.pack (name plug),x)) sheet
      where 
       sheet :: Maybe Worksheet
       sheet = case matrix of
                 Nothing -> Nothing
                 Just m -> Just def{_wsCells = fromRows . numberList . Prelude.map numberList $ m }
            where 
              numberList :: [c] -> [(Int, c)]
              numberList = zip [1..] 
       matrix :: Maybe  [[Cell]]
       matrix = 
         case plug of
           TblSQL{} -> if length (fields plug) > 1
                       then Just $ headers ++ content
                       else Nothing
           BinSQL{} -> -- trace ("## Warning: Handling of link-tables isn't correct yet. Therefor, sheet`"++name plug++"` doesn't contain proper info") $
                       Just $ headers ++ content
           ScalarSQL{} -> Nothing
         where
           headers :: [[Cell]]
           headers = transpose (Prelude.map f (zip (True : repeat False) (plugFields plug))) 
             where f :: (Bool,SqlField) -> [Cell]
                   f (isFirstField,fld) = Prelude.map toCell 
                         [ if isFirstField  -- In case of the first field of the table, we put the fieldname inbetween brackets,
                                            -- to be able to find the population again by the reader of the .xlsx file
                           then Just $ "["++name fld++"]" 
                           else Just . cleanUpRelName $
                                          case plug of
                                            TblSQL{}    -> name fld
                                            BinSQL{}    -> name plug
                                            ScalarSQL{} -> fatal 57 "ScalarSQL not expected here"
                         , Just $ name .target . fldexpr $ fld ]
                   cleanUpRelName :: String -> String
                   --TODO: This is a not-so-nice way to get the relationname from the fieldname.
                   cleanUpRelName orig
                     | isPrefixOf "tgt_" orig = drop 4 orig
                     | isPrefixOf "src_" orig = drop 4 orig ++"~" --TODO: Make in less hacky! (See also the way the fieldname is constructed.
                     | otherwise         = orig
           content = fmap record2Cells (tableContents fSpec plug)
           record2Cells :: [Maybe AAtomValue] -> [Cell]
           record2Cells = map record2Cell
           record2Cell :: Maybe AAtomValue -> Cell
           record2Cell mVal = Cell Nothing (case mVal of
                                             Nothing -> Nothing
                                             Just aVal -> Just $
                                                case aVal of
                                                  AAVString _ str -> CellText $ T.pack str
                                                  AAVInteger _ int -> CellDouble (fromInteger int)
                                                  AAVFloat _ x -> CellDouble x
                                                  AAVBoolean _ b -> CellBool b
                                                  AAVDate _ day -> (CellDouble . fromInteger) (diffDays (fromGregorian 1900 1 1) day)
                                                  _ -> fatal 87 $ "Content found that cannot be converted to Excel (jet)." 
                                           )  
       toCell :: Maybe String -> Cell
       toCell mVal 
        = Cell { _cellStyle = Nothing
               , _cellValue = fmap (\x -> CellText . T.pack $ x) mVal
               }