{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveGeneric #-} module Codec.Xlsx.Writer.Internal.PivotTable ( PivotTableFiles(..) , renderPivotTableFiles ) where import Data.ByteString.Lazy (ByteString) import Data.List.Extra (nubOrd) import qualified Data.Map as M import Data.Maybe (catMaybes, fromMaybe, mapMaybe) import Data.Text (Text) import GHC.Generics (Generic) import Safe (fromJustNote) import Text.XML import Codec.Xlsx.Types.Cell import Codec.Xlsx.Types.Common import Codec.Xlsx.Types.PivotTable import Codec.Xlsx.Types.PivotTable.Internal import Codec.Xlsx.Writer.Internal data PivotTableFiles = PivotTableFiles { pvtfTable :: ByteString , pvtfCacheDefinition :: ByteString } deriving (Eq, Show, Generic) data CacheDefinition = CacheDefinition { cdSourceRef :: CellRef , cdSourceSheet :: Text , cdFields :: [CacheField] } deriving (Eq, Show, Generic) renderPivotTableFiles :: CellMap -> Int -> PivotTable -> PivotTableFiles renderPivotTableFiles cm cacheId t = PivotTableFiles {..} where pvtfTable = renderLBS def $ ptDefinitionDocument cacheId cache t cache = generateCache cm t pvtfCacheDefinition = renderLBS def $ toDocument cache ptDefinitionDocument :: Int -> CacheDefinition -> PivotTable -> Document ptDefinitionDocument cacheId cache t = documentFromElement "Pivot table generated by xlsx" $ ptDefinitionElement "pivotTableDefinition" cacheId cache t ptDefinitionElement :: Name -> Int -> CacheDefinition -> PivotTable -> Element ptDefinitionElement nm cacheId cache PivotTable {..} = elementList nm attrs elements where attrs = catMaybes [ "colGrandTotals" .=? justFalse _pvtColumnGrandTotals , "rowGrandTotals" .=? justFalse _pvtRowGrandTotals , "outline" .=? justTrue _pvtOutline , "outlineData" .=? justTrue _pvtOutlineData ] ++ [ "name" .= _pvtName , "dataCaption" .= _pvtDataCaption , "cacheId" .= cacheId , "dataOnRows" .= (DataPosition `elem` _pvtRowFields) ] elements = [location, pivotFields, rowFields, colFields, dataFields] location = leafElement "location" [ "ref" .= _pvtLocation -- TODO : set proper , "firstHeaderRow" .= (1 :: Int) , "firstDataRow" .= (2 :: Int) , "firstDataCol" .= (1 :: Int) ] name2x = M.fromList $ zip (mapMaybe _pfiName _pvtFields) [0 ..] mapFieldToX f = fromJustNote "no field" $ M.lookup f name2x pivotFields = elementListSimple "pivotFields" $ map pFieldEl _pvtFields maybeFieldIn Nothing _ = False maybeFieldIn (Just name) positions = FieldPosition name `elem` positions pFieldEl PivotFieldInfo { _pfiName = fName , _pfiOutline = outline , _pfiSortType = sortType , _pfiHiddenItems = hidden } | fName `maybeFieldIn` _pvtRowFields = pFieldEl' fName outline ("axisRow" :: Text) hidden sortType | fName `maybeFieldIn` _pvtColumnFields = pFieldEl' fName outline ("axisCol" :: Text) hidden sortType | otherwise = leafElement "pivotField" $ [ "dataField" .= True , "showAll" .= False , "outline" .= outline] ++ catMaybes ["name" .=? fName] pFieldEl' fName outline axis hidden sortType = elementList "pivotField" ([ "axis" .= axis , "showAll" .= False , "outline" .= outline ] ++ catMaybes [ "name" .=? fName , "sortType" .=? justNonDef FieldSortManual sortType]) [ elementListSimple "items" $ items fName hidden ++ [leafElement "item" ["t" .= ("default" :: Text)]] ] items Nothing _ = [] items (Just fName) hidden = [ itemEl x item hidden | (x, item) <- zip [0 ..] . fromMaybe [] $ M.lookup fName cachedItems ] itemEl x item hidden = leafElement "item" $ ["x" .= (x :: Int)] ++ catMaybes ["h" .=? justTrue (item `elem` hidden)] cachedItems = M.fromList $ [(cfName, cfItems) | CacheField {..} <- cdFields cache] rowFields = elementListSimple "rowFields" . map fieldEl $ if length _pvtDataFields > 1 then _pvtRowFields else filter (/= DataPosition) _pvtRowFields colFields = elementListSimple "colFields" $ map fieldEl _pvtColumnFields fieldEl p = leafElement "field" ["x" .= fieldPos p] fieldPos DataPosition = (-2) :: Int fieldPos (FieldPosition f) = mapFieldToX f dataFields = elementListSimple "dataFields" $ map dFieldEl _pvtDataFields dFieldEl DataField {..} = leafElement "dataField" $ catMaybes [ "name" .=? Just _dfName , "fld" .=? Just (mapFieldToX _dfField) , "subtotal" .=? justNonDef ConsolidateSum _dfFunction ] generateCache :: CellMap -> PivotTable -> CacheDefinition generateCache cm PivotTable {..} = CacheDefinition { cdSourceRef = _pvtSrcRef , cdSourceSheet = _pvtSrcSheet , cdFields = cachedFields } where cachedFields = mapMaybe (fmap cache . _pfiName) _pvtFields cache name = CacheField { cfName = name , cfItems = fromJustNote "specified pivot table field does not exist" $ M.lookup name itemsByName } ((r1, c1), (r2, c2)) = fromJustNote "Invalid src ref of pivot table " $ fromRange _pvtSrcRef getCellValue ix = M.lookup ix cm >>= _cellValue itemsByName = M.fromList $ flip mapMaybe [c1 .. c2] $ \c -> do CellText nm <- getCellValue (r1, c) let values = mapMaybe (\r -> getCellValue (r, c)) [(r1 + 1) .. r2] return (PivotFieldName nm, nubOrd values) instance ToDocument CacheDefinition where toDocument = documentFromElement "Pivot cache definition generated by xlsx" . toElement "pivotCacheDefinition" instance ToElement CacheDefinition where toElement nm CacheDefinition {..} = elementList nm attrs elements where attrs = ["invalid" .= True, "refreshOnLoad" .= True] elements = [worksheetSource, cacheFields] worksheetSource = elementList "cacheSource" ["type" .= ("worksheet" :: Text)] [ leafElement "worksheetSource" ["ref" .= cdSourceRef, "sheet" .= cdSourceSheet] ] cacheFields = elementListSimple "cacheFields" $ map (toElement "cacheField") cdFields