Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 148: add newtype for column and row indices. #161

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions src/Codec/Xlsx/Formatted.hs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ data Formatted = Formatted {
--
-- If you don't already have a 'StyleSheet' you want to use as starting point
-- then 'minimalStyleSheet' is a good choice.
formatted :: Map (Int, Int) FormattedCell -> StyleSheet -> Formatted
formatted :: Map (RowIndex, ColumnIndex) FormattedCell -> StyleSheet -> Formatted
formatted cs styleSheet =
let initSt = stateFromStyleSheet styleSheet
(cs', finalSt) = runState (mapM (uncurry formatCell) (M.toList cs)) initSt
Expand All @@ -246,7 +246,8 @@ formatted cs styleSheet =
}

-- | Build an 'Xlsx', render provided cells as per the 'StyleSheet'.
formatWorkbook :: [(Text, Map (Int, Int) FormattedCell)] -> StyleSheet -> Xlsx
formatWorkbook ::
[(Text, Map (RowIndex, ColumnIndex) FormattedCell)] -> StyleSheet -> Xlsx
formatWorkbook nfcss initStyle = extract go
where
initSt = stateFromStyleSheet initStyle
Expand All @@ -263,7 +264,7 @@ formatWorkbook nfcss initStyle = extract go

-- | reverse to 'formatted' which allows to get a map of formatted cells
-- from an existing worksheet and its workbook's style sheet
toFormattedCells :: CellMap -> [Range] -> StyleSheet -> Map (Int, Int) FormattedCell
toFormattedCells :: CellMap -> [Range] -> StyleSheet -> Map (RowIndex, ColumnIndex) FormattedCell
toFormattedCells m merges StyleSheet{..} = applyMerges $ M.map toFormattedCell m
where
toFormattedCell cell@Cell{..} =
Expand Down Expand Up @@ -301,11 +302,14 @@ toFormattedCells m merges StyleSheet{..} = applyMerges $ M.map toFormattedCell m
if apply then prop cXf else fail "not applied"
applyMerges cells = foldl' onlyTopLeft cells merges
onlyTopLeft cells range = flip execState cells $ do
let ((r1, c1), (r2, c2)) = fromJustNote "fromRange" $ fromRange range
let ((r1, c1), (r2, c2)) =
fromJustNote "fromRange" $ fromRange range
nonTopLeft = tail [(r, c) | r<-[r1..r2], c<-[c1..c2]]
forM_ nonTopLeft (modify . M.delete)
at (r1, c1) . non def . formattedRowSpan .= (r2 - r1 +1)
at (r1, c1) . non def . formattedColSpan .= (c2 - c1 +1)
at (r1, c1) . non def . formattedRowSpan .=
(unRowIndex r2 - unRowIndex r1 + 1)
at (r1, c1) . non def . formattedColSpan .=
(unColumnIndex c2 - unColumnIndex c1 + 1)

data CondFormatted = CondFormatted {
-- | The resulting stylesheet
Expand Down Expand Up @@ -334,13 +338,15 @@ conditionallyFormatted cfs styleSheet = CondFormatted
-------------------------------------------------------------------------------}

-- | Format a cell with (potentially) rowspan or colspan
formatCell :: (Int, Int) -> FormattedCell -> State FormattingState [((Int, Int), Cell)]
formatCell :: (RowIndex, ColumnIndex) -> FormattedCell
-> State FormattingState [((RowIndex, ColumnIndex), Cell)]
formatCell (row, col) cell = do
let (block, mMerge) = cellBlock (row, col) cell
forM_ mMerge $ \merge -> formattingMerges %= (:) merge
mapM go block
where
go :: ((Int, Int), FormattedCell) -> State FormattingState ((Int, Int), Cell)
go :: ((RowIndex, ColumnIndex), FormattedCell)
-> State FormattingState ((RowIndex, ColumnIndex), Cell)
go (pos, c@FormattedCell{..}) = do
styleId <- cellStyleId c
return (pos, _formattedCell{_cellStyle = styleId})
Expand All @@ -354,11 +360,11 @@ formatCell (row, col) cell = do
-- remaining cells are the cells covered by the rowspan/colspan.
--
-- Also returns the cell merge instruction, if any.
cellBlock :: (Int, Int) -> FormattedCell
-> ([((Int, Int), FormattedCell)], Maybe Range)
cellBlock :: (RowIndex, ColumnIndex) -> FormattedCell
-> ([((RowIndex, ColumnIndex), FormattedCell)], Maybe Range)
cellBlock (row, col) cell@FormattedCell{..} = (block, merge)
where
block :: [((Int, Int), FormattedCell)]
block :: [((RowIndex, ColumnIndex), FormattedCell)]
block = [ ((row', col'), cellAt (row', col'))
| row' <- [topRow .. bottomRow]
, col' <- [leftCol .. rightCol]
Expand All @@ -368,26 +374,27 @@ cellBlock (row, col) cell@FormattedCell{..} = (block, merge)
merge = do guard (topRow /= bottomRow || leftCol /= rightCol)
return $ mkRange (topRow, leftCol) (bottomRow, rightCol)

cellAt :: (Int, Int) -> FormattedCell
cellAt :: (RowIndex, ColumnIndex) -> FormattedCell
cellAt (row', col') =
if row' == row && col == col'
then cell
else def & formattedFormat . formatBorder ?~ borderAt (row', col')

border = _formatBorder _formattedFormat

borderAt :: (Int, Int) -> Border
borderAt :: (RowIndex, ColumnIndex) -> Border
borderAt (row', col') = def
& borderTop .~ do guard (row' == topRow) ; _borderTop =<< border
& borderBottom .~ do guard (row' == bottomRow) ; _borderBottom =<< border
& borderLeft .~ do guard (col' == leftCol) ; _borderLeft =<< border
& borderRight .~ do guard (col' == rightCol) ; _borderRight =<< border

topRow, bottomRow, leftCol, rightCol :: Int
topRow, bottomRow :: RowIndex
leftCol, rightCol :: ColumnIndex
topRow = row
bottomRow = row + _formattedRowSpan - 1
bottomRow = RowIndex $ unRowIndex row + _formattedRowSpan - 1
leftCol = col
rightCol = col + _formattedColSpan - 1
rightCol = ColumnIndex $ unColumnIndex col + _formattedColSpan - 1

cellStyleId :: FormattedCell -> State FormattingState (Maybe Int)
cellStyleId c = mapM (getId formattingCellXfs) =<< constructCellXf c
Expand Down
18 changes: 9 additions & 9 deletions src/Codec/Xlsx/Lens.hs
Original file line number Diff line number Diff line change
Expand Up @@ -74,43 +74,43 @@ atSheet s = xlSheets . \f -> fmap unSheetList . at s f . SheetList
-- | lens giving access to a cell in some worksheet
-- by its position, by default row+column index is used
-- so this lens is a synonym of 'ixCellRC'
ixCell :: (Int, Int) -> Traversal' Worksheet Cell
ixCell :: (RowIndex, ColumnIndex) -> Traversal' Worksheet Cell
ixCell = ixCellRC

-- | lens to access cell in a worksheet
ixCellRC :: (Int, Int) -> Traversal' Worksheet Cell
ixCellRC :: (RowIndex, ColumnIndex) -> Traversal' Worksheet Cell
ixCellRC i = wsCells . ix i

-- | lens to access cell in a worksheet using more traditional
-- x+y coordinates
ixCellXY :: (Int, Int) -> Traversal' Worksheet Cell
ixCellXY :: (ColumnIndex, RowIndex) -> Traversal' Worksheet Cell
ixCellXY i = ixCellRC $ swap i

-- | accessor that can read, write or delete cell in a worksheet
-- synonym of 'atCellRC' so uses row+column index
atCell :: (Int, Int) -> Lens' Worksheet (Maybe Cell)
atCell :: (RowIndex, ColumnIndex) -> Lens' Worksheet (Maybe Cell)
atCell = atCellRC

-- | lens to read, write or delete cell in a worksheet
atCellRC :: (Int, Int) -> Lens' Worksheet (Maybe Cell)
atCellRC :: (RowIndex, ColumnIndex) -> Lens' Worksheet (Maybe Cell)
atCellRC i = wsCells . at i

-- | lens to read, write or delete cell in a worksheet
-- using more traditional x+y or row+column index
atCellXY :: (Int, Int) -> Lens' Worksheet (Maybe Cell)
atCellXY :: (ColumnIndex, RowIndex) -> Lens' Worksheet (Maybe Cell)
atCellXY i = atCellRC $ swap i

-- | lens to read, write or delete cell value in a worksheet
-- with row+column coordinates, synonym for 'cellValueRC'
cellValueAt :: (Int, Int) -> Lens' Worksheet (Maybe CellValue)
cellValueAt :: (RowIndex, ColumnIndex) -> Lens' Worksheet (Maybe CellValue)
cellValueAt = cellValueAtRC

-- | lens to read, write or delete cell value in a worksheet
-- using row+column coordinates of that cell
cellValueAtRC :: (Int, Int) -> Lens' Worksheet (Maybe CellValue)
cellValueAtRC :: (RowIndex, ColumnIndex) -> Lens' Worksheet (Maybe CellValue)
cellValueAtRC i = atCell i . non def . cellValue

-- | lens to read, write or delete cell value in a worksheet
-- using traditional x+y coordinates
cellValueAtXY :: (Int, Int) -> Lens' Worksheet (Maybe CellValue)
cellValueAtXY :: (ColumnIndex, RowIndex) -> Lens' Worksheet (Maybe CellValue)
cellValueAtXY i = cellValueAtRC $ swap i
38 changes: 19 additions & 19 deletions src/Codec/Xlsx/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,18 @@ extractSheetFast ar sst contentTypes caches wf = do
justNonEmpty _ = Nothing
collectRows = foldr collectRow (M.empty, M.empty, M.empty)
collectRow ::
( Int
( RowIndex
, Maybe RowProperties
, [(Int, Int, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))])
-> ( Map Int RowProperties
, [(RowIndex, ColumnIndex, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))])
-> ( Map RowIndex RowProperties
, CellMap
, Map SharedFormulaIndex SharedFormulaOptions)
-> ( Map Int RowProperties
-> ( Map RowIndex RowProperties
, CellMap
, Map SharedFormulaIndex SharedFormulaOptions)
collectRow (r, mRP, rowCells) (rowMap, cellMap, sharedF) =
let (newCells0, newSharedF0) =
unzip [(((x, y), cd), shared) | (x, y, cd, shared) <- rowCells]
unzip [(((rInd, cInd), cd), shared) | (rInd, cInd, cd, shared) <- rowCells]
newCells = M.fromAscList newCells0
newSharedF = M.fromAscList $ catMaybes newSharedF0
newRowMap =
Expand All @@ -270,10 +270,10 @@ extractSheetFast ar sst contentTypes caches wf = do
in (newRowMap, cellMap <> newCells, sharedF <> newSharedF)
parseRow ::
Xeno.Node
-> Either Text ( Int
-> Either Text ( RowIndex
, Maybe RowProperties
, [( Int
, Int
, [( RowIndex
, ColumnIndex
, Cell
, Maybe (SharedFormulaIndex, SharedFormulaOptions))])
parseRow row = do
Expand All @@ -294,7 +294,7 @@ extractSheetFast ar sst contentTypes caches wf = do
cellNodes <- collectChildren row $ childList "c"
cells <- forM cellNodes parseCell
return
( r
( RowIndex r
, if props == def
then Nothing
else Just props
Expand All @@ -310,8 +310,8 @@ extractSheetFast ar sst contentTypes caches wf = do
-- </xsd:complexType>
parseCell ::
Xeno.Node
-> Either Text ( Int
, Int
-> Either Text ( RowIndex
, ColumnIndex
, Cell
, Maybe (SharedFormulaIndex, SharedFormulaOptions))
parseCell cell = do
Expand Down Expand Up @@ -384,11 +384,11 @@ extractSheet ar sst contentTypes caches wf = do
collect $ cur $/ element (n_ "sheetData") &/ element (n_ "row") >=> parseRow
parseRow ::
Cursor
-> [( Int
-> [( RowIndex
, Maybe RowProperties
, [(Int, Int, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))])]
, [(RowIndex, ColumnIndex, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))])]
parseRow c = do
r <- fromAttribute "r" c
r <- RowIndex <$> fromAttribute "r" c
let prop = RowProps
{ rowHeight = do h <- listToMaybe $ fromAttribute "ht" c
case fromAttribute "customHeight" c of
Expand All @@ -406,7 +406,7 @@ extractSheet ar sst contentTypes caches wf = do
)
parseCell ::
Cursor
-> [(Int, Int, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))]
-> [(RowIndex, ColumnIndex, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))]
parseCell cell = do
ref <- fromAttribute "r" cell
let s = listToMaybe $ cell $| attribute "s" >=> decimal
Expand All @@ -427,11 +427,11 @@ extractSheet ar sst contentTypes caches wf = do
return (r, c, Cell s d comment f, shared)
collect = foldr collectRow (M.empty, M.empty, M.empty)
collectRow ::
( Int
( RowIndex
, Maybe RowProperties
, [(Int, Int, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))])
-> (Map Int RowProperties, CellMap, Map SharedFormulaIndex SharedFormulaOptions)
-> (Map Int RowProperties, CellMap, Map SharedFormulaIndex SharedFormulaOptions)
, [(RowIndex, ColumnIndex, Cell, Maybe (SharedFormulaIndex, SharedFormulaOptions))])
-> (Map RowIndex RowProperties, CellMap, Map SharedFormulaIndex SharedFormulaOptions)
-> (Map RowIndex RowProperties, CellMap, Map SharedFormulaIndex SharedFormulaOptions)
collectRow (r, mRP, rowCells) (rowMap, cellMap, sharedF) =
let (newCells0, newSharedF0) =
unzip [(((x,y),cd), shared) | (x, y, cd, shared) <- rowCells]
Expand Down
10 changes: 5 additions & 5 deletions src/Codec/Xlsx/Parser/Stream.hs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ data SheetItem = MkSheetItem
deriving anyclass NFData

data Row = MkRow
{ _ri_row_index :: Int -- ^ Row number
{ _ri_row_index :: RowIndex -- ^ Row number
, _ri_cell_row :: ~CellRow -- ^ Row itself
} deriving stock (Generic, Show)
deriving anyclass NFData
Expand Down Expand Up @@ -162,8 +162,8 @@ data ExcelValueType
data SheetState = MkSheetState
{ _ps_row :: ~CellRow -- ^ Current row
, _ps_sheet_index :: Int -- ^ Current sheet ID (AKA 'sheetInfoSheetId')
, _ps_cell_row_index :: Int -- ^ Current row number
, _ps_cell_col_index :: Int -- ^ Current column number
, _ps_cell_row_index :: RowIndex -- ^ Current row number
, _ps_cell_col_index :: ColumnIndex -- ^ Current column number
, _ps_cell_style :: Maybe Int
, _ps_is_in_val :: Bool -- ^ Flag for indexing wheter the parser is in value or not
, _ps_shared_strings :: SharedStringsMap -- ^ Shared string map
Expand Down Expand Up @@ -549,7 +549,7 @@ addCellToRow txt = do
style <- use ps_cell_style
when (_ps_is_in_val st) $ do
val <- liftEither $ first ParseCellError $ parseValue (_ps_shared_strings st) txt (_ps_type st)
put $ st { _ps_row = IntMap.insert (_ps_cell_col_index st)
put $ st { _ps_row = IntMap.insert (unColumnIndex $ _ps_cell_col_index st)
(Cell { _cellStyle = style
, _cellValue = Just val
, _cellComment = Nothing
Expand Down Expand Up @@ -709,7 +709,7 @@ parseType list =

-- | Parse coordinates from a list of xml elements if such were found on "r" key
{-# SCC parseCoordinates #-}
parseCoordinates :: SheetValues -> Either CoordinateErrors (Int, Int)
parseCoordinates :: SheetValues -> Either CoordinateErrors (RowIndex, ColumnIndex)
parseCoordinates list = do
(_nm, valText) <- maybe (Left $ CoordinateNotFound list) Right $ findName "r" list
maybe (Left $ DecodeFailure valText list) Right $ fromSingleCellRef $ CellRef valText
7 changes: 4 additions & 3 deletions src/Codec/Xlsx/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ instance ToAttrVal SheetState where
-- | Xlsx worksheet
data Worksheet = Worksheet
{ _wsColumnsProperties :: [ColumnsProperties] -- ^ column widths
, _wsRowPropertiesMap :: Map Int RowProperties -- ^ custom row properties (height, style) map
, _wsRowPropertiesMap :: Map RowIndex RowProperties
-- ^ custom row properties (height, style) map
, _wsCells :: CellMap -- ^ data mapped by (row, column) pairs
, _wsDrawing :: Maybe Drawing -- ^ SpreadsheetML Drawing
, _wsMerges :: [Range] -- ^ list of cell merges
Expand Down Expand Up @@ -389,7 +390,7 @@ parseStyleSheet (Styles bs) = parseLBS def bs >>= parseDoc

-- | converts cells mapped by (row, column) into rows which contain
-- row index and cells as pairs of column indices and cell values
toRows :: CellMap -> [(Int, [(Int, Cell)])]
toRows :: CellMap -> [(RowIndex, [(ColumnIndex, Cell)])]
toRows cells =
map extractRow $ groupBy ((==) `on` (fst . fst)) $ M.toList cells
where
Expand All @@ -398,7 +399,7 @@ toRows cells =
extractRow _ = error "invalid CellMap row"

-- | reverse to 'toRows'
fromRows :: [(Int, [(Int, Cell)])] -> CellMap
fromRows :: [(RowIndex, [(ColumnIndex, Cell)])] -> CellMap
fromRows rows = M.fromList $ concatMap mapRow rows
where
mapRow (r, cells) = map (\(c, v) -> ((r, c), v)) cells
Expand Down
2 changes: 1 addition & 1 deletion src/Codec/Xlsx/Types/Cell.hs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ makeLenses ''Cell
-- | Map containing cell values which are indexed by row and column
-- if you need to use more traditional (x,y) indexing please you could
-- use corresponding accessors from ''Codec.Xlsx.Lens''
type CellMap = Map (Int, Int) Cell
type CellMap = Map (RowIndex, ColumnIndex) Cell

{-------------------------------------------------------------------------------
Parsing
Expand Down
Loading