packages feed

xlsx 0.4.1 → 0.4.2

raw patch · 9 files changed

+418/−39 lines, 9 files

Files

CHANGELOG.markdown view
@@ -1,3 +1,12 @@+0.4.2+-----+* added basic tables support+* fixed boolean element parsing for rich text run properties (thanks+  laurent stephane <laurent_step@yahoo.fr> for reporting)+* fixed problem of `cwStyle` not being optional (thanks laurent+  stephane <laurent_step@yahoo.fr> for reporting)+* added basic autofilter support+ 0.4.1 ----- * fixed serialization problem of empty validations and pivot caches
src/Codec/Xlsx/Parser.hs view
@@ -157,6 +157,12 @@       validations = M.fromList . map unDvPair $           cur $/ element (n_ "dataValidations") &/ element (n_ "dataValidation") >=> fromCursor +      tableIds =+        cur $/ element (n_ "tableParts") &/ element (n_ "tablePart") >=>+        fromAttribute (odr "id")++  let mAutoFilter = listToMaybe $ cur $/ element (n_ "autoFilter") >=> fromCursor+   mDrawing <- case mDrawingId of       Just dId -> do           rel <- note (InvalidRef filePath dId) $ Relationships.lookup dId sheetRels@@ -171,8 +177,25 @@     note (InconsistentXlsx $ "Bad pivot table in " <> T.pack ptPath) $       parsePivotTable (flip Prelude.lookup caches) bs -  return $ Worksheet cws rowProps cells mDrawing merges sheetViews pageSetup condFormtattings validations pTables+  tables <- forM tableIds $ \rId -> do+    fp <- lookupRelPath filePath sheetRels rId+    getTable ar fp +  return $+    Worksheet+      cws+      rowProps+      cells+      mDrawing+      merges+      sheetViews+      pageSetup+      condFormtattings+      validations+      pTables+      mAutoFilter+      tables+ extractCellValue :: SharedStringTable -> Text -> Text -> [CellValue] extractCellValue sst "s" v =     case T.decimal v of@@ -319,6 +342,10 @@       return (cacheId, sources)   return (sheets, DefinedNames names, caches) +getTable :: Zip.Archive -> FilePath -> Parser Table+getTable ar fp = do+  cur <- xmlCursorRequired ar fp+  headErr (InvalidFile fp) (fromCursor cur)  worksheetFile :: FilePath -> Relationships -> Text -> RefId -> Parser WorksheetFile worksheetFile parentPath wbRels name rId =
src/Codec/Xlsx/Types.hs view
@@ -32,6 +32,8 @@     , wsConditionalFormattings     , wsDataValidations     , wsPivotTables+    , wsAutoFilter+    , wsTables     -- ** Cells     , cellValue     , cellStyle@@ -65,6 +67,7 @@ import           Text.XML.Cursor  import           Codec.Xlsx.Parser.Internal+import           Codec.Xlsx.Types.AutoFilter            as X import           Codec.Xlsx.Types.Comment               as X import           Codec.Xlsx.Types.Common                as X import           Codec.Xlsx.Types.ConditionalFormatting as X@@ -77,6 +80,7 @@ import           Codec.Xlsx.Types.RichText              as X import           Codec.Xlsx.Types.SheetViews            as X import           Codec.Xlsx.Types.StyleSheet            as X+import           Codec.Xlsx.Types.Table                 as X import           Codec.Xlsx.Types.Variant               as X import           Codec.Xlsx.Writer.Internal @@ -129,19 +133,30 @@  -- | Column range (from cwMin to cwMax) width data ColumnsWidth = ColumnsWidth-    { cwMin   :: Int-    , cwMax   :: Int-    , cwWidth :: Double-    , cwStyle :: Int-    } deriving (Eq, Show)+  { cwMin :: Int+  -- ^ First column affected by this 'ColumnWidth' record.+  , cwMax :: Int+  -- ^ Last column affected by this 'ColumnWidth' record.+  , cwWidth :: Double+  -- ^ Column width measured as the number of characters of the+  -- maximum digit width of the numbers 0, 1, 2, ..., 9 as rendered in+  -- the normal style's font.+  --+  -- See longer description in Section 18.3.1.13 "col (Column Width &+  -- Formatting)" (p. 1605)+  , cwStyle :: Maybe Int+  -- ^ Default style for the affected column(s). Affects cells not yet+  -- allocated in the column(s).  In other words, this style applies+  -- to new columns.+  } deriving (Eq, Show)  instance FromCursor ColumnsWidth where-    fromCursor c = do-      cwMin <- decimal =<< attribute "min" c-      cwMax <- decimal =<< attribute "max" c-      cwWidth <- rational =<< attribute "width" c-      cwStyle <- decimal =<< attribute "style" c-      return ColumnsWidth{..}+  fromCursor c = do+    cwMin <- fromAttribute "min" c+    cwMax <- fromAttribute "max" c+    cwWidth <- fromAttribute "width" c+    cwStyle <- maybeAttribute "style" c+    return ColumnsWidth {..}  -- | Xlsx worksheet data Worksheet = Worksheet@@ -155,12 +170,14 @@   , _wsConditionalFormattings :: Map SqRef ConditionalFormatting   , _wsDataValidations :: Map SqRef DataValidation   , _wsPivotTables :: [PivotTable]+  , _wsAutoFilter :: Maybe AutoFilter+  , _wsTables :: [Table]   } deriving (Eq, Show)  makeLenses ''Worksheet  instance Default Worksheet where-    def = Worksheet [] M.empty M.empty Nothing [] Nothing Nothing M.empty M.empty []+    def = Worksheet [] M.empty M.empty Nothing [] Nothing Nothing M.empty M.empty [] Nothing []  newtype Styles = Styles {unStyles :: L.ByteString}             deriving (Eq, Show)
+ src/Codec/Xlsx/Types/AutoFilter.hs view
@@ -0,0 +1,165 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+module Codec.Xlsx.Types.AutoFilter where++import Control.Lens (makeLenses)+import Data.Default+import Data.Map (Map)+import Data.Maybe (catMaybes)+import qualified Data.Map as M+import Data.Text (Text)+import Text.XML+import Text.XML.Cursor++import Codec.Xlsx.Parser.Internal+import Codec.Xlsx.Types.Common+import Codec.Xlsx.Writer.Internal++-- | The filterColumn collection identifies a particular column in the+-- AutoFilter range and specifies filter information that has been+-- applied to this column. If a column in the AutoFilter range has no+-- criteria specified, then there is no corresponding filterColumn+-- collection expressed for that column.+--+-- Section 18.3.2.7 "filterColumn (AutoFilter Column)" (p. 1717)+data FilterColumn+  = Filters { _fltValues :: [Text]}+  | ACustomFilter CustomFilter+  | CustomFiltersOr CustomFilter+                    CustomFilter+  | CustomFiltersAnd CustomFilter+                     CustomFilter+  deriving (Eq, Show)++data CustomFilter = CustomFilter+  { cfltOperator :: CustomFilterOperator+  , cfltValue :: Text+  } deriving (Eq, Show)++data CustomFilterOperator+  = FltrEqual+    -- ^ Show results which are equal to criteria.+  | FltrGreaterThan+    -- ^ Show results which are greater than criteria.+  | FltrGreaterThanOrEqual+    -- ^ Show results which are greater than or equal to criteria.+  | FltrLessThan+    -- ^ Show results which are less than criteria.+  | FltrLessThanOrEqual+    -- ^ Show results which are less than or equal to criteria.+  | FltrNotEqual+    -- ^ Show results which are not equal to criteria.+  deriving (Eq, Show)++-- | AutoFilter temporarily hides rows based on a filter criteria,+-- which is applied column by column to a table of datain the+-- worksheet.+--+-- TODO: sortState, extList+--+-- Section 18.3.1.2 "autoFilter (AutoFilter Settings)" (p. 1596)+data AutoFilter = AutoFilter+  { _afRef :: Maybe CellRef+  , _afFilterColumns :: Map Int FilterColumn+  } deriving (Eq, Show)++makeLenses ''AutoFilter+++{-------------------------------------------------------------------------------+  Default instances+-------------------------------------------------------------------------------}++instance Default AutoFilter where+    def = AutoFilter Nothing M.empty++{-------------------------------------------------------------------------------+  Parsing+-------------------------------------------------------------------------------}++instance FromCursor AutoFilter where+  fromCursor cur = do+    _afRef <- maybeAttribute "ref" cur+    let _afFilterColumns = M.fromList $ cur $/ element (n_ "filterColumn") >=> \c -> do+          colId <- fromAttribute "colId" c+          fcol <- c $/ anyElement >=> fltColFromNode . node+          return (colId, fcol)+    return AutoFilter {..}++fltColFromNode :: Node -> [FilterColumn]+fltColFromNode n | n `nodeElNameIs` (n_ "filters") = do+                     let _fltValues = cur $/ element (n_ "filter") >=> fromAttribute "val"+                     return Filters{..}+                 | n `nodeElNameIs` (n_ "customFilters") = do+                     isAnd <- fromAttributeDef "and" False cur+                     let cFilters = cur $/ element (n_ "customFilter") >=> \c -> do+                           op <- fromAttributeDef "operator" FltrEqual c+                           val <- fromAttribute "val" c+                           return $ CustomFilter op val+                     case cFilters of+                       [f] ->+                         return $ ACustomFilter f+                       [f1, f2] ->+                         if isAnd+                           then return $ CustomFiltersAnd f1 f2+                           else return $ CustomFiltersOr f1 f2+                       _ ->+                         fail "bad custom filter"+                 | otherwise = fail "no matching nodes"+  where+    cur = fromNode n++instance FromAttrVal CustomFilterOperator where+  fromAttrVal "equal" = readSuccess FltrEqual+  fromAttrVal "greaterThan" = readSuccess FltrGreaterThan+  fromAttrVal "greaterThanOrEqual" = readSuccess FltrGreaterThanOrEqual+  fromAttrVal "lessThan" = readSuccess FltrLessThan+  fromAttrVal "lessThanOrEqual" = readSuccess FltrLessThanOrEqual+  fromAttrVal "notEqual" = readSuccess FltrNotEqual+  fromAttrVal t = invalidText "CustomFilterOperator" t++{-------------------------------------------------------------------------------+  Rendering+-------------------------------------------------------------------------------}++instance ToElement AutoFilter where+  toElement nm AutoFilter {..} =+    elementList+      nm+      (catMaybes ["ref" .=? _afRef])+      [ elementList+        (n_ "filterColumn")+        ["colId" .= colId]+        [fltColToElement fCol]+      | (colId, fCol) <- M.toList _afFilterColumns+      ]++fltColToElement :: FilterColumn -> Element+fltColToElement Filters {..} =+  elementListSimple+    (n_ "filters")+    [leafElement (n_ "filter") ["val" .= v] | v <- _fltValues]+fltColToElement (ACustomFilter f) =+  elementListSimple (n_ "customFilters") [toElement (n_ "customFilter") f]+fltColToElement (CustomFiltersOr f1 f2) =+  elementListSimple+    (n_ "customFilters")+    [toElement (n_ "customFilter") f | f <- [f1, f2]]+fltColToElement (CustomFiltersAnd f1 f2) =+  elementList+    (n_ "customFilters")+    ["and" .= True]+    [toElement (n_ "customFilter") f | f <- [f1, f2]]++instance ToElement CustomFilter where+  toElement nm CustomFilter {..} =+    leafElement nm ["operator" .= cfltOperator, "val" .= cfltValue]++instance ToAttrVal CustomFilterOperator where+  toAttrVal FltrEqual = "equal"+  toAttrVal FltrGreaterThan = "greaterThan"+  toAttrVal FltrGreaterThanOrEqual = "greaterThanOrEqual"+  toAttrVal FltrLessThan = "lessThan"+  toAttrVal FltrLessThanOrEqual = "lessThanOrEqual"+  toAttrVal FltrNotEqual = "notEqual"
src/Codec/Xlsx/Types/RichText.hs view
@@ -265,13 +265,13 @@     _runPropertiesFont          <- maybeElementValue (n_ "rFont") cur     _runPropertiesCharset       <- maybeElementValue (n_ "charset") cur     _runPropertiesFontFamily    <- maybeElementValue (n_ "family") cur-    _runPropertiesBold          <- maybeElementValue (n_ "b") cur-    _runPropertiesItalic        <- maybeElementValue (n_ "i") cur-    _runPropertiesStrikeThrough <- maybeElementValue (n_ "strike") cur-    _runPropertiesOutline       <- maybeElementValue (n_ "outline") cur-    _runPropertiesShadow        <- maybeElementValue (n_ "shadow") cur-    _runPropertiesCondense      <- maybeElementValue (n_ "condense") cur-    _runPropertiesExtend        <- maybeElementValue (n_ "extend") cur+    _runPropertiesBold          <- maybeBoolElementValue (n_ "b") cur+    _runPropertiesItalic        <- maybeBoolElementValue (n_ "i") cur+    _runPropertiesStrikeThrough <- maybeBoolElementValue (n_ "strike") cur+    _runPropertiesOutline       <- maybeBoolElementValue (n_ "outline") cur+    _runPropertiesShadow        <- maybeBoolElementValue (n_ "shadow") cur+    _runPropertiesCondense      <- maybeBoolElementValue (n_ "condense") cur+    _runPropertiesExtend        <- maybeBoolElementValue (n_ "extend") cur     _runPropertiesColor         <- maybeFromElement  (n_ "color") cur     _runPropertiesSize          <- maybeElementValue (n_ "sz") cur     _runPropertiesUnderline     <- maybeElementValue (n_ "u") cur
+ src/Codec/Xlsx/Types/Table.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+module Codec.Xlsx.Types.Table where++import Control.Lens (makeLenses)+import Data.Maybe (maybeToList)+import Data.Text (Text)+import Text.XML+import Text.XML.Cursor++#if !MIN_VERSION_base(4,8,0)+import           Control.Applicative+#endif++import Codec.Xlsx.Parser.Internal+import Codec.Xlsx.Types.AutoFilter+import Codec.Xlsx.Types.Common+import Codec.Xlsx.Writer.Internal++-- | Tables are ranges of data in the worksheet that have special+-- behavior applied which allow users to better sort, analyze, format,+-- manage, add, and delete data. Tables and table columns can also be+-- referenced through formulas by the spreadsheet application using+-- friendly names, making formula calculations that use tables much+-- easier to understand and maintain. Tables provide a natural way for+-- working with large sets of tabular data.+--+-- NOTE: as @headerRowCount@ property isn't yet supported it's+-- supposed that it's library user liability to guarantee that the 1st+-- row of 'tblRef' range contains cells with names specified in+-- `tblColumns`+--+-- Section 18.5 \"Tables\" (p. 1728)+-- Section 18.5.1 \"Tables\" (p. 1729)+-- Section 18.5.1.2 "table (Table)" (p. 1730)+data Table = Table+  { tblDisplayName :: Text+    -- ^ A string representing the name of the table. This is the name+    -- that shall be used in formula references, and displayed in the UI+    -- to the spreadsheet user.  This name shall not have any spaces in+    -- it, and it shall be unique amongst all other displayNames and+    -- definedNames in the workbook. The character lengths and+    -- restrictions are the same as for definedNames .+  , tblName :: Text+    -- ^ A string representing the name of the table that is used to+    -- reference the table programmatically through the spreadsheet+    -- applications object model. This string shall be unique per table+    -- per sheet. It has the same length and character restrictions as+    -- for displayName.  By default this should be the same as the+    -- table's 'tblDisplayName' . This name should also be kept in synch with+    -- the displayName when the displayName is updated in the UI by the+    -- spreadsheet user.+  , tblRef :: CellRef+    -- ^ The range on the relevant sheet that the table occupies+    -- expressed using A1 style referencing.+  , tblColumns :: [TableColumn]+    -- ^ columns of this table, specification requires any table to+    -- include at least 1 column+  , tblAutoFilter :: Maybe AutoFilter+  } deriving (Eq, Show)++-- | Single table column+--+-- TODO: styling information+--+-- Section 18.5.1.3 "tableColumn (Table Column)" (p. 1735)+data TableColumn = TableColumn+  { tblcName :: Text+  -- ^ A string representing the unique caption of the table+  -- column. This is what shall be displayed in the header row in the+  -- UI, and is referenced through functions. This name shall be+  -- unique per table.+  } deriving (Eq, Show)++makeLenses ''Table++{-------------------------------------------------------------------------------+  Parsing+-------------------------------------------------------------------------------}++instance FromCursor Table where+  fromCursor c = do+    tblDisplayName <- fromAttribute "displayName" c+    tblName <- fromAttribute "name" c+    tblRef <- fromAttribute "ref" c+    tblAutoFilter <- maybeFromElement (n_ "autoFilter") c+    let tblColumns =+          c $/ element (n_ "tableColumns") &/ element (n_ "tableColumn") >=>+          fmap TableColumn . fromAttribute "name"+    return Table {..}++{-------------------------------------------------------------------------------+  Rendering+-------------------------------------------------------------------------------}++tableToDocument :: Table -> Int -> Document+tableToDocument tbl i =+  documentFromElement "Table generated by xlsx" $+  tableToElement "table" tbl i++tableToElement :: Name -> Table -> Int -> Element+tableToElement nm Table {..} i = elementList nm attrs subElements+  where+    attrs =+      [ "id" .= i+      , "displayName" .= tblDisplayName+      , "name" .= tblName+      , "ref" .= tblRef+      ]+    subElements =+      maybeToList (toElement "autoFilter" <$> tblAutoFilter) +++      [ countedElementList+          "tableColumns"+          [ leafElement "tableColumn" ["id" .= i', "name" .= tblcName c]+          | (i', c) <- zip [(1 :: Int) ..] tblColumns+          ]+      ]
src/Codec/Xlsx/Writer.hs view
@@ -87,14 +87,24 @@     workbookFiles = bookFiles xlsx     sheetNames = xlsx ^. xlSheets . to (map fst) -singleSheetFiles :: Int -> Cells -> [FileData] -> Worksheet -> (FileData, [FileData])-singleSheetFiles n cells pivFileDatas ws = runST $ do+singleSheetFiles :: Int+                 -> Cells+                 -> [FileData]+                 -> Worksheet+                 -> STRef s Int+                 -> ST s (FileData, [FileData])+singleSheetFiles n cells pivFileDatas ws tblIdRef = do     ref <- newSTRef 1     mCmntData <- genComments n cells ref     mDrawingData <- maybe (return Nothing) (fmap Just . genDrawing n ref) (ws ^. wsDrawing)     pivRefs <- forM pivFileDatas $ \fd -> do       refId <- nextRefId ref       return (refId, fd)+    refTables <- forM (_wsTables ws) $ \tbl -> do+      refId <- nextRefId ref+      tblId <- readSTRef tblIdRef+      modifySTRef' tblIdRef (+1)+      return (refId, genTable tbl tblId)     let sheetFilePath = "xl/worksheets/sheet" <> show n <> ".xml"         sheetFile = FileData sheetFilePath             "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"@@ -108,19 +118,22 @@             [ elementListSimple "sheetViews" . map (toElement "sheetView") <$> ws ^. wsSheetViews             , nonEmptyElListSimple "cols" . map cwEl $ ws ^. wsColumns             , Just . elementListSimple "sheetData" $ sheetDataXml cells (ws ^. wsRowPropertiesMap)+            , toElement "autoFilter" <$> (ws ^. wsAutoFilter)             , nonEmptyElListSimple "mergeCells" . map mergeE1 $ ws ^. wsMerges             ] ++ map (Just . toElement "conditionalFormatting") cfPairs ++             [ nonEmptyElListSimple "dataValidations" $ map (toElement "dataValidation") dvPairs             , toElement "pageSetup" <$> ws ^. wsPageSetup             , fst3 <$> mDrawingData             , fst <$> mCmntData+            , nonEmptyElListSimple "tableParts"+                [leafElement "tablePart" [odr "id" .= rId] | (rId, _) <- refTables]             ]         cfPairs = map CfPair . M.toList $ ws ^. wsConditionalFormattings         dvPairs = map DvPair . M.toList $ ws ^. wsDataValidations-        cwEl cw = leafElement "col" [ ("min", txti $ cwMin cw)-                                    , ("max", txti $ cwMax cw)-                                    , ("width", txtd $ cwWidth cw)-                                    , ("style", txti $ cwStyle cw)]+        cwEl cw =+          leafElement "col" $+          ["min" .= cwMin cw, "max" .= cwMax cw, "width" .= cwWidth cw] +++          catMaybes ["style" .=? (justNonDef 0 =<< cwStyle cw)]         mergeE1 r = leafElement "mergeCell" [("ref" .= r)]          sheetRels = if null referencedFiles@@ -133,7 +146,8 @@             | (i, FileData{..}) <- referenced ]         referenced = fromMaybe [] (snd <$> mCmntData) ++                      catMaybes [ snd3 <$> mDrawingData ] ++-                     pivRefs+                     pivRefs +++                     refTables         referencedFiles = map snd referenced         extraFiles = maybe [] thd3 mDrawingData         otherFiles = sheetRels ++ referencedFiles ++ extraFiles@@ -208,7 +222,8 @@   return (el, (rId, drawingFile), referenced)   where     drawingFilePath = "xl/drawings/drawing" <> show n <> ".xml"-    drawingFile = FileData drawingFilePath (smlCT "drawing") "drawing" drawingXml+    drawingCT = "application/vnd.openxmlformats-officedocument.drawing+xml"+    drawingFile = FileData drawingFilePath drawingCT "drawing" drawingXml     drawingXml = renderLBS def{rsNamespaces=nss} $ toDocument dr'     nss = [ ("xdr", "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing")           , ("a",   "http://schemas.openxmlformats.org/drawingml/2006/main")@@ -322,6 +337,14 @@             renderRels [refFileDataToRel tablePath (unsafeRefId 1, cacheFile)]       in ((cacheId, cacheFile), tableFile, [tableRels]) +genTable :: Table -> Int -> FileData+genTable tbl tblId = FileData{..}+  where+    fdPath = "xl/tables/table" <> show tblId <> ".xml"+    fdContentType = smlCT "table"+    fdRelType = "table"+    fdContents = renderLBS def $ tableToDocument tbl tblId+ data FileData = FileData { fdPath        :: FilePath                          , fdContentType :: Text                          , fdRelType     :: Text@@ -434,9 +457,10 @@         generatePivotFiles (xlsx ^. xlSheets . to (map (^. _2 . wsPivotTables)))       sheetCells = map (transformSheetData shared) sheets       sheetInputs = zip3 sheetCells sheetPivotTables sheets+  tblIdRef <- newSTRef 1   allSheetFiles <- forM (zip [1..] sheetInputs) $ \(i, (cells, pvTables, sheet)) -> do     rId <- nextRefId ref-    let (sheetFile, others) = singleSheetFiles i cells pvTables sheet+    (sheetFile, others) <- singleSheetFiles i cells pvTables sheet tblIdRef     return ((rId, sheetFile), others)   let sheetFiles = map fst allSheetFiles       sheetNameByRId = zip (map fst sheetFiles) (xlsx ^. xlSheets . to (map fst))
test/DataTest.hs view
@@ -111,7 +111,22 @@   where     sheets =       [("List1", sheet1), ("Another sheet", sheet2), ("with pivot table", pvSheet)]-    sheet1 = Worksheet cols rowProps testCellMap1 drawing ranges sheetViews pageSetup cFormatting validations []+    sheet1 = Worksheet cols rowProps testCellMap1 drawing ranges+      sheetViews pageSetup cFormatting validations [] (Just autoFilter) tables+    autoFilter = def & afRef ?~ CellRef "A1:E10"+                     & afFilterColumns .~ fCols+    fCols = M.fromList [ (1, Filters ["a","b","ZZZ"])+                       , (2, CustomFiltersAnd (CustomFilter FltrGreaterThanOrEqual "0")+                           (CustomFilter FltrLessThan "42"))]+    tables =+      [ Table+        { tblName = "Table1"+        , tblDisplayName = "Table1"+        , tblRef = CellRef "A3"+        , tblColumns = [TableColumn "another text"]+        , tblAutoFilter = Just (def & afRef ?~ CellRef "A3")+        }+      ]     sheet2 = def & wsCells .~ testCellMap2     pvSheet = sheetWithPvCells & wsPivotTables .~ [testPivotTable]     sheetWithPvCells = flip execState def $ do@@ -122,7 +137,7 @@       cellValueAt (2, 3) ?= CellDouble 12.34       cellValueAt (2, 4) ?= CellDouble 42     rowProps = M.fromList [(1, RowProps (Just 50) (Just 3))]-    cols = [ColumnsWidth 1 10 15 1]+    cols = [ColumnsWidth 1 10 15 (Just 1)]     drawing = Just $ testDrawing { _xdrAnchors = map resolve $ _xdrAnchors testDrawing }     resolve :: Anchor RefId RefId -> Anchor FileInfo ChartSpace     resolve Anchor {..} =@@ -263,7 +278,8 @@   where     rich = [ RichTextRun              { _richTextRunProperties =-               Just $ def & runPropertiesCharset ?~ 1+               Just $ def & runPropertiesBold ?~ True+                          & runPropertiesCharset ?~ 1                           & runPropertiesColor ?~ def -- TODO: why not Nothing here?                           & runPropertiesFont ?~ "Calibri"                           & runPropertiesScheme ?~ FontSchemeMinor
xlsx.cabal view
@@ -1,6 +1,6 @@ Name:                xlsx -Version:             0.4.1+Version:             0.4.2  Synopsis:            Simple and incomplete Excel file parser/writer Description:@@ -35,6 +35,12 @@   ghc-options:       -Wall   Exposed-modules:   Codec.Xlsx                    , Codec.Xlsx.Types+                   , Codec.Xlsx.Formatted+                   , Codec.Xlsx.Lens+                   , Codec.Xlsx.Parser+                   , Codec.Xlsx.Parser.Internal+                   , Codec.Xlsx.Parser.Internal.PivotTable+                   , Codec.Xlsx.Types.AutoFilter                    , Codec.Xlsx.Types.Comment                    , Codec.Xlsx.Types.Common                    , Codec.Xlsx.Types.ConditionalFormatting@@ -51,18 +57,14 @@                    , Codec.Xlsx.Types.Internal.NumFmtPair                    , Codec.Xlsx.Types.Internal.Relationships                    , Codec.Xlsx.Types.Internal.SharedStringTable+                   , Codec.Xlsx.Types.PageSetup                    , Codec.Xlsx.Types.PivotTable                    , Codec.Xlsx.Types.PivotTable.Internal                    , Codec.Xlsx.Types.RichText                    , Codec.Xlsx.Types.SheetViews-                   , Codec.Xlsx.Types.PageSetup                    , Codec.Xlsx.Types.StyleSheet+                   , Codec.Xlsx.Types.Table                    , Codec.Xlsx.Types.Variant-                   , Codec.Xlsx.Lens-                   , Codec.Xlsx.Parser-                   , Codec.Xlsx.Parser.Internal-                   , Codec.Xlsx.Parser.Internal.PivotTable-                   , Codec.Xlsx.Formatted                    , Codec.Xlsx.Writer                    , Codec.Xlsx.Writer.Internal                    , Codec.Xlsx.Writer.Internal.PivotTable