xls 0.1.2 → 0.1.3
raw patch · 7 files changed
+108/−27 lines, 7 filesdep +hspecdep ~filepathdep ~resourcetdep ~transformersbinary-addedPVP ok
version bump matches the API change (PVP)
Dependencies added: hspec
Dependency ranges changed: filepath, resourcet, transformers
API changes (from Hackage documentation)
+ Data.Xls: decodeXlsIO :: FilePath -> IO [[[String]]]
Files
- Changelog.md +3/−0
- README.md +12/−6
- bin/xls2csv.hs +7/−12
- lib/Data/Xls.hs +73/−7
- test/Spec.hs +10/−1
- test/data/test.xls binary
- xls.cabal +3/−1
Changelog.md view
@@ -1,3 +1,6 @@+## 0.1.3+* Add IO version of `decodeXls` function.+ ## 0.1.2 * Upgrade libxls upstream package version from 1.4.0 to 1.5.1
README.md view
@@ -1,5 +1,9 @@ ## Haskell xls Parsing +[](https://hackage.haskell.org/package/xls)+[](https://travis-ci.com/harendra-kumar/xls)+[](https://ci.appveyor.com/project/harendra-kumar/xls)+ `xls` is a Haskell library to parse Microsoft Excel spreadsheet files. It parses the xls file format (extension `.xls`) more specifically known as `BIFF/Excel 97-2004`.@@ -7,15 +11,17 @@ It can be useful for mining data from old Microsoft Excel spreadsheets. ## API-Use `decodeXls` to get a streaming Conduit. For example to convert an-xls file to comma separated csv:+Use `decodeXlsIO` to get a list of all worksheets. For example to convert all+worksheets in an xls file to comma separated csv: ```haskell+import Data.List (intercalate)+import Data.Xls (decodeXlsIO)+ xlsToCSV :: String -> IO ()-xlsToCSV file =- runResourceT- $ decodeXls file- $$ CL.mapM_ (liftIO . putStrLn . intercalate ",")+xlsToCSV file = do+ worksheets <- decodeXlsIO file+ mapM_ (mapM_ (putStrLn . intercalate ",")) worksheets ``` An `xls2csv` utility is shipped with the package.
bin/xls2csv.hs view
@@ -1,21 +1,16 @@ #!/usr/bin/env stack -- stack --resolver lts runhaskell --package getopt-generics -import Control.Monad.IO.Class-import Control.Monad.Trans.Resource-import Data.Conduit-import Data.Conduit.List as CL-import Data.List (intercalate)-import Data.Xls-import WithCli+import Data.List (intercalate)+import Data.Xls (decodeXlsIO)+import WithCli (withCli) -- TODO need to escape the separator and the escaping quotes themselves xlsToCSV :: String -> IO ()-xlsToCSV file =- runResourceT- $ runConduit- $ decodeXls file- .| CL.mapM_ (liftIO . putStrLn . intercalate ",")+xlsToCSV file = do+ worksheets <- decodeXlsIO file+ mapM_ (mapM_ (putStrLn . intercalate ",")) worksheets +main :: IO () main = withCli xlsToCSV
lib/Data/Xls.hs view
@@ -19,15 +19,21 @@ {-# LANGUAGE DeriveDataTypeable #-} #endif -module Data.Xls (decodeXls, XlsException(..)) where+module Data.Xls+ ( decodeXlsIO+ , decodeXls+ , XlsException(..)+ )+where -import Control.Exception (Exception, throwIO)+import Control.Exception (Exception, throwIO, bracket) import Control.Monad.IO.Class+import Control.Monad (when, void) import Control.Monad.Trans.Resource-import Data.Conduit hiding (Conduit, Sink, Source)+import Data.Conduit hiding (Conduit, Sink, Source) import Data.Data import Data.Int-import Data.Maybe (catMaybes, fromJust, isJust)+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe) import Foreign.C import Foreign.Ptr import Text.Printf@@ -80,8 +86,9 @@ -- rows in a worksheet. Each row represented by a list of Strings, each String -- representing an individual cell. ----- Currently there is no separation of worksheets, all worksheets in a--- workbook get concatenated.+-- Important Note: This API concatenates rows from all worksheets into a single+-- stream. Please use the non-streaming 'decodeXlsIO' API to get individual+-- worksheets. -- -- Throws 'XlsException' --@@ -104,6 +111,27 @@ count <- liftIO $ c_xls_wb_sheetcount pWB mapM_ (decodeOneWorkSheet file pWB) [0 .. count - 1] ++-- | Parse a Microsoft excel xls workbook file into a list of worksheets, each+-- worksheet consists of a list of rows and each row consists of a list of+-- cells. Cells are plain 'String'.+--+-- Throws 'XlsException'+--+decodeXlsIO+ :: FilePath+ -> IO [[[String]]]+decodeXlsIO file = do+ file' <- newCString file+ pWB <- newCString "UTF-8" >>= c_xls_open file'+ when (pWB == nullPtr) $+ throwIO $ XlsFileNotFound+ $ "XLS file " ++ file ++ " not found."+ count <- liftIO $ c_xls_wb_sheetcount pWB+ results <- mapM (decodeOneWorkSheetIO file pWB) [0 .. count - 1]+ void $ c_xls_close_WB pWB+ return results+ decodeOneWorkSheet :: MonadResource m => FilePath -> XLSWorkbook -> CInt -> ConduitM i [String] m ()@@ -123,19 +151,57 @@ decodeWS = decodeRows +decodeOneWorkSheetIO+ :: FilePath+ -> XLSWorkbook+ -> CInt+ -> IO [[String]]+decodeOneWorkSheetIO file pWB index =+ bracket alloc cleanup decodeRowsIO+ where+ alloc = do+ pWS <- c_xls_getWorkSheet pWB index+ if pWS == nullPtr then+ throwIO $ XlsParseError+ $ "XLS file "+ ++ file+ ++ " could not be parsed."+ else do+ c_xls_parseWorkSheet pWS+ return pWS+ cleanup = c_xls_close_WS+ decodeRows :: MonadResource m => XLSWorksheet -> ConduitM i [String] m () decodeRows pWS = do rows <- liftIO $ c_xls_ws_rowcount pWS cols <- liftIO $ c_xls_ws_colcount pWS mapM_ (decodeOneRow pWS cols) [r | r <- [0 .. rows - 1]] +decodeRowsIO+ :: XLSWorksheet+ -> IO [[String]]+decodeRowsIO pWS = do+ rows <- c_xls_ws_rowcount pWS+ cols <- c_xls_ws_colcount pWS+ mapM (decodeOneRowIO pWS cols) [r | r <- [0 .. rows - 1]]+ decodeOneRow :: MonadResource m => XLSWorksheet -> Int16 -> Int16 -> ConduitM i [String] m () decodeOneRow pWS cols rowindex = mapM (liftIO . (c_xls_cell pWS rowindex)) [0 .. cols - 1]- >>= mapM (liftIO. decodeOneCell)+ >>= mapM (liftIO . decodeOneCell) >>= yield . catMaybes++decodeOneRowIO+ :: XLSWorksheet+ -> Int16+ -> Int16+ -> IO [String]+decodeOneRowIO pWS cols rowindex =+ mapM (c_xls_cell pWS rowindex) [0 .. cols - 1]+ >>= mapM decodeOneCell+ >>= pure . (map $ fromMaybe "") data CellType = Numerical | Formula | Str | Other
test/Spec.hs view
@@ -1,2 +1,11 @@+import Data.Xls+import Test.Hspec+ main :: IO ()-main = putStrLn "Test suite not yet implemented"+main = hspec $ describe "Sanity check" $ do+ it "Test file parsing" $ do+ content <- decodeXlsIO "test/data/test.xls"+ content `shouldBe` testFileContent++testFileContent :: [[[String]]]+testFileContent = [[["1.000000000000000","2.3","text"]],[["1.000000000000000","2.3","text"]]]
+ test/data/test.xls view
binary file changed (absent → 6144 bytes)
xls.cabal view
@@ -1,5 +1,5 @@ name: xls-version: 0.1.2+version: 0.1.3 synopsis: Parse Microsoft Excel xls files (BIFF/Excel 97-2004) description: Parse Microsoft Excel spreadsheet files in @.xls@ file format@@ -29,6 +29,7 @@ lib/libxls/include/*.h lib/libxls/include/libxls/*.h lib/libxls/include/libxls/*.c.h+ test/data/test.xls flag force-has-iconv description: force using iconv library on Windows@@ -82,6 +83,7 @@ main-is: Spec.hs build-depends: base >= 4.7 && < 5 , xls+ , hspec ghc-options: -threaded -rtsopts -with-rtsopts=-N source-repository head