packages feed

csv-conduit 0.7.3.0 → 1.0.1.1

raw patch · 13 files changed

Files

LICENSE view
@@ -1,3 +1,4 @@+Copyright (c)2024, Daniel Vianna Copyright (c)2013, Ozgun Ataman Copyright (c)2012, Johan Tibell 
README.md view
@@ -1,4 +1,7 @@-# README [![Build Status](https://travis-ci.org/ozataman/csv-conduit.svg?branch=master)](https://travis-ci.org/ozataman/csv-conduit)+# README+[![cabal+build](https://github.com/ozataman/csv-conduit/actions/workflows/cabal.yml/badge.svg)](https://github.com/ozataman/csv-conduit/actions)+[![stack build](https://github.com/ozataman/csv-conduit/actions/workflows/stack.yml/badge.svg)](https://github.com/ozataman/csv-conduit/actions)  ## CSV Files and Haskell @@ -50,7 +53,7 @@ - Dmitry Dzhus (@dzhus) - Niklas Hambüchen (@nh2) - Facundo Domínguez (@facundominguez)-+- Daniel Vianna (@dmvianna)  ### Introduction @@ -90,9 +93,9 @@ myProcessor = CL.map reverse  test :: IO ()-test = runResourceT $ -  transformCSV defCSVSettings -               (sourceFile "input.csv") +test = runResourceT $+  transformCSV defCSVSettings+               (sourceFile "input.csv")                myProcessor                (sinkFile "output.csv") ```@@ -113,11 +116,10 @@ -- Let's simply stream from a file, parse the CSV, reserialize it -- and push back into another file. test :: IO ()-test = runResourceT $ -  sourceFile "test/BigFile.csv" $= +test = runResourceT $+  sourceFile "test/BigFile.csv" $=   intoCSV defCSVSettings $=   myProcessor $=   fromCSV defCSVSettings $$   sinkFile "test/BigFileOut.csv" ```-
changelog.md view
@@ -1,3 +1,27 @@+1.0.1.1+* Fix test suite to build with text 2.1.2 and ghc 9.12.2, resolving [#62](https://github.com/ozataman/csv-conduit/issues/62)++1.0.1.0+* Use ConduitT instead of ConduitM (prettier type inference with newer conduit imports)++1.0.0.2+* Fixed [#17](https://github.com/ozataman/csv-conduit/issues/17),+  where CSV created with Excel in Mac OS failed to parse due to its+  newline characters.++1.0.0.1+* Removed dependencies: mmorph, monad-control, mtl,+  unordered-containers, primitive++1.0.0.0+* Removed `return` from the `Monad` instance for `Parser`, and+  transfered its definition to `pure` in the `Applicative` instance of+  `Parser`. This was necessary to support GHC 9.6.4.+* Create new API to choose whether to handle empty CSV cells as empty+  strings or NULLs.+* Added imports that were removed from `Prelude` in GHC 9.6.4.+* Bumped the default Stack resolver to LTS-22.20.+ 0.7.3.0 * Add ordered versions of named records for consistent, controllable header column ordering. [PR 44](https://github.com/ozataman/csv-conduit/pull/44) * Add support for GHC 9.0.1
csv-conduit.cabal view
@@ -1,27 +1,34 @@-Name:                csv-conduit-Version:             0.7.3.0-Synopsis:            A flexible, fast, conduit-based CSV parser library for Haskell.-Homepage:            http://github.com/ozataman/csv-conduit-License:             BSD3-License-file:        LICENSE-Author:              Ozgun Ataman-Maintainer:          Ozgun Ataman <ozataman@gmail.com>-Category:            Data, Conduit, CSV, Text-Build-type:          Simple-Cabal-version:       >= 1.10-Tested-with:         GHC == 9.0.1, GHC == 8.10.4, GHC == 8.8.4, GHC == 8.8.3, GHC == 8.6.5, GHC == 8.4.4, GHC == 8.2.2-Description:+name:               csv-conduit+version:            1.0.1.1+synopsis:+  A flexible, fast, conduit-based CSV parser library for Haskell.++homepage:           http://github.com/ozataman/csv-conduit+license:            BSD3+license-file:       LICENSE+author:             Ozgun Ataman+maintainer:         Daniel Vianna <dmvianna@gmail.com>+category:           Data, Conduit, CSV, Text+build-type:         Simple+cabal-version:      >=1.10+tested-with:+  GHC ==8.2.2+   || ==8.4.4+   || ==8.6.5+   || ==8.8.3+   || ==8.8.4+   || ==8.10.4+   || ==9.0.1+   || ==9.6.4++description:   CSV files are the de-facto standard in many situations involving data transfer,   particularly when dealing with enterprise application or disparate database   systems.-   .-   While there are a number of CSV libraries in Haskell, at the time of this   project's start in 2010, there wasn't one that provided all of the following:-   .-   * Full flexibility in quote characters, separators, input/output   .   * Constant space operation@@ -32,106 +39,94 @@   .   * Fast operation   .-   This library is an attempt to close these gaps. Please note that   this library started its life based on the enumerator package and   has recently been ported to work with conduits instead. In the   process, it has been greatly simplified thanks to the modular nature   of the conduits library.-   .-   Following the port to conduits, the library has also gained the   ability to parameterize on the stream type and work both with   ByteString and Text.-   .-   For more documentation and examples, check out the README at:   .-   <http://github.com/ozataman/csv-conduit>   . - extra-source-files:-  README.md   changelog.md+  README.md+  test/test-mac-excel.csv+  test/test-windows-excel.csv   test/test.csv   test/Test.hs+  test/test.xls  flag lib-Werror   default: False-  manual: True+  manual:  True  library   default-language: Haskell2010   exposed-modules:-      Data.CSV.Conduit-      Data.CSV.Conduit.Types-      Data.CSV.Conduit.Conversion-      Data.CSV.Conduit.Parser.ByteString-      Data.CSV.Conduit.Parser.Text+    Data.CSV.Conduit+    Data.CSV.Conduit.Conversion+    Data.CSV.Conduit.Parser.ByteString+    Data.CSV.Conduit.Parser.Text+    Data.CSV.Conduit.Types+   other-modules:-      Data.CSV.Conduit.Conversion.Internal-      Data.CSV.Conduit.Monoid-  ghc-options: -Wall -funbox-strict-fields-  if flag(lib-Werror)+    Data.CSV.Conduit.Conversion.Internal+    Data.CSV.Conduit.Monoid++  ghc-options:      -Wall -funbox-strict-fields++  if flag(lib-werror)     ghc-options: -Werror-  hs-source-dirs: src++  hs-source-dirs:   src   build-depends:-      attoparsec             >= 0.10-    , base                   >= 4 && < 5+      array+    , attoparsec          >=0.10+    , base                >=4       && <5+    , blaze-builder     , bytestring-    , conduit                >= 1.2.8+    , conduit             >=1.3.0   && <2.0     , conduit-extra-    , containers             >= 0.3-    , exceptions             >= 0.3-    , monad-control-    , text+    , containers          >=0.3     , data-default-    , vector-    , array-    , blaze-builder-    , unordered-containers+    , exceptions          >=0.3     , ordered-containers-    , transformers-    , mtl-    , mmorph     , primitive-    , resourcet              >= 1.1.2.1-    , semigroups--  if impl(ghc >= 7.2.1)-    cpp-options: -DGENERICS-    build-depends: ghc-prim >= 0.2-+    , resourcet           >=1.1.2.1+    , text+    , transformers+    , vector  test-suite test   default-language: Haskell2010-  type: exitcode-stdio-1.0-  main-is: Test.hs-  ghc-options: -Wall-  if flag(lib-Werror)+  type:             exitcode-stdio-1.0+  main-is:          Test.hs+  ghc-options:      -Wall -Wunused-packages++  if flag(lib-werror)     ghc-options: -Werror-  hs-source-dirs: test++  hs-source-dirs:   test   build-depends:-      base >= 4 && < 5+      base                  >=4     && <5     , bytestring-    , conduit >= 1.3.0-    , containers >= 0.3+    , conduit               >=1.3.0+    , containers            >=0.3     , csv-conduit     , directory-    , vector-    , HUnit >= 1.2+    , HUnit                 >=1.2+    , ordered-containers     , test-framework     , test-framework-hunit     , text-    , ordered-containers-    , transformers-    , mtl-    , primitive-+    , vector  source-repository head   type:     git
src/Data/CSV/Conduit.hs view
@@ -6,7 +6,6 @@ {-# LANGUAGE OverloadedStrings     #-} {-# LANGUAGE RankNTypes            #-} {-# LANGUAGE ScopedTypeVariables   #-}-{-# LANGUAGE TypeSynonymInstances  #-} {-# LANGUAGE UndecidableInstances  #-}  module Data.CSV.Conduit@@ -36,11 +35,12 @@  ------------------------------------------------------------------------------- import           Control.Exception-import           Control.Monad.Catch.Pure           (CatchT)-import           Control.Monad.Catch.Pure           (runCatchT)-import           Control.Monad.Except+import           Control.Monad.Catch.Pure           (runCatchT, CatchT)+import           Control.Monad.IO.Class             (MonadIO (liftIO)) import           Control.Monad.Primitive import           Control.Monad.ST+import           Control.Monad.Trans.Class          (MonadTrans(lift))+import           Control.Monad.Trans.Except         (ExceptT(..), runExceptT) import           Control.Monad.Trans.Resource       (MonadResource, MonadThrow,                                                      runResourceT) import           Data.Attoparsec.Types              (Parser)@@ -135,13 +135,13 @@   -----------------------------------------------------------------------------   -- | Turn a stream of 's' into a stream of CSV row type. An example   -- would be parsing a ByteString stream as rows of 'MapRow' 'Text'.-  intoCSV :: (MonadThrow m) => CSVSettings -> ConduitM s r m ()+  intoCSV :: (MonadThrow m) => CSVSettings -> ConduitT s r m ()    -----------------------------------------------------------------------------   -- | Turn a stream of CSV row type back into a stream of 's'. An   -- example would be rendering a stream of 'Row' 'ByteString' rows as   -- 'Text'.-  fromCSV :: Monad m => CSVSettings -> ConduitM r s m ()+  fromCSV :: Monad m => CSVSettings -> ConduitT r s m ()   @@ -153,9 +153,12 @@   rowToStr s !r =     let       sep = B.pack [c2w (csvSep s)]-      wrapField !f = case csvQuoteChar s of-        Just !x-> (x `B8.cons` escape x f) `B8.snoc` x-        _      -> f+      wrapField !f = case csvQuoteCharAndStyle s of+        Just (x, quoteEmpty) ->+          case quoteEmpty == DoQuoteEmpty || B8.length f /= 0 of+            True -> (x `B8.cons` escape x f) `B8.snoc` x+            False -> f+        Nothing   -> f       escape c str = B8.intercalate (B8.pack [c,c]) $ B8.split c str     in B.intercalate sep . map wrapField $ r @@ -169,9 +172,11 @@   rowToStr s !r =     let       sep = T.pack [csvSep s]-      wrapField !f = case csvQuoteChar s of-        Just !x-> x `T.cons` escape x f `T.snoc` x-        _      -> f+      wrapField !f = case csvQuoteCharAndStyle s of+        Just (x, quoteEmpty) -> case quoteEmpty == DoQuoteEmpty || not (T.null f) of+          True -> x `T.cons` escape x f `T.snoc` x+          False -> f+        Nothing -> f       escape c str = T.intercalate (T.pack [c,c]) $ T.split (== c) str     in T.intercalate sep . map wrapField $ r @@ -208,13 +213,13 @@  ------------------------------------------------------------------------------- fromCSVRow :: (Monad m, IsString s, CSV s r)-           => CSVSettings -> ConduitM r s m ()+           => CSVSettings -> ConduitT r s m () fromCSVRow set = awaitForever $ \row -> mapM_ yield [rowToStr set row, "\n"]    --------------------------------------------------------------------------------intoCSVRow :: (MonadThrow m, AttoparsecInput i) => Parser i (Maybe o) -> ConduitM i o m ()+intoCSVRow :: (MonadThrow m, AttoparsecInput i) => Parser i (Maybe o) -> ConduitT i o m () intoCSVRow p = parse .| puller   where     parse = {-# SCC "conduitParser_p" #-} conduitParser p@@ -238,7 +243,7 @@  ------------------------------------------------------------------------------- intoCSVMap :: (Ord a, MonadThrow m, CSV s [a])-           => CSVSettings -> ConduitM s (MapRow a) m ()+           => CSVSettings -> ConduitT s (MapRow a) m () intoCSVMap set = intoCSV set .| (headers >>= converter)   where     headers = do@@ -293,7 +298,7 @@  ------------------------------------------------------------------------------- fromCSVMap :: (Monad m, IsString s, CSV s [a])-           => CSVSettings -> ConduitM (M.Map k a) s m ()+           => CSVSettings -> ConduitT (M.Map k a) s m () fromCSVMap set = awaitForever push   where     push r = mapM_ yield [rowToStr set (M.elems r), "\n"]@@ -316,7 +321,7 @@ writeHeaders     :: (Monad m, CSV s (Row r), IsString s)     => CSVSettings-    -> ConduitM (MapRow r) s m ()+    -> ConduitT (MapRow r) s m () writeHeaders set = do   mrow <- await   case mrow of@@ -375,14 +380,14 @@     -> Either SomeException (v a) decodeCSV set bs = runST $ runExceptT pipeline   where-    src :: ConduitM () s (ExceptT SomeException (ST s1)) ()+    src :: ConduitT () s (ExceptT SomeException (ST s1)) ()     src = C.sourceList [bs]-    csvConvert :: ConduitM s a (ExceptT SomeException (ST s1)) ()+    csvConvert :: ConduitT s a (ExceptT SomeException (ST s1)) ()     csvConvert = transPipe (ExceptT . runCatchT) csvConvert'-    csvConvert' :: ConduitM s a (CatchT (ST s1)) ()+    csvConvert' :: ConduitT s a (CatchT (ST s1)) ()     csvConvert' = intoCSV set     growthFactor = 10-    sink :: ConduitM a Void.Void (ExceptT SomeException (ST s1)) (v a)+    sink :: ConduitT a Void.Void (ExceptT SomeException (ST s1)) (v a)     sink = sinkVector growthFactor     pipeline :: ExceptT SomeException (ST s1) (v a)     pipeline = runConduit (src .| csvConvert .| sink)@@ -442,11 +447,11 @@     :: (MonadThrow m, CSV s a, CSV s' b)     => CSVSettings     -- ^ Settings to be used for both input and output-    -> ConduitM () s m ()+    -> ConduitT () s m ()     -- ^ A raw stream data source. Ex: 'sourceFile inFile'-    -> ConduitM a b m ()+    -> ConduitT a b m ()     -- ^ A transforming conduit-    -> ConduitM s' Void.Void m ()+    -> ConduitT s' Void.Void m ()     -- ^ A raw stream data sink. Ex: 'sinkFile outFile'     -> m () transformCSV set = transformCSV' set set@@ -470,11 +475,11 @@     -- ^ Settings to be used for input     -> CSVSettings     -- ^ Settings to be used for output-    -> ConduitM () s m ()+    -> ConduitT () s m ()     -- ^ A raw stream data source. Ex: 'sourceFile inFile'-    -> ConduitM a b m ()+    -> ConduitT a b m ()     -- ^ A transforming conduit-    -> ConduitM s' Void.Void m ()+    -> ConduitT s' Void.Void m ()     -- ^ A raw stream data sink. Ex: 'sinkFile outFile'     -> m () transformCSV' setIn setOut source c sink = runConduit $@@ -495,7 +500,7 @@  ------------------------------------------------------------------------------- -- | An efficient sink that incrementally grows a vector from the input stream-sinkVector :: (PrimMonad m, GV.Vector v a) => Int -> ConduitM a o m (v a)+sinkVector :: (PrimMonad m, GV.Vector v a) => Int -> ConduitT a o m (v a) sinkVector by = do     v <- lift $ GMV.new by     go 0 v
src/Data/CSV/Conduit/Conversion.hs view
@@ -68,7 +68,6 @@ import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as L import Data.Int (Int8, Int16, Int32, Int64)-import Data.Kind (Type) import qualified Data.Map as M import qualified Data.Map.Ordered as MO import Data.Semigroup as Semigroup@@ -160,7 +159,7 @@ -- >         | otherwise     = mzero class FromRecord a where     parseRecord :: Record -> Parser a-  + #ifdef GENERICS     default parseRecord :: (Generic a, GFromRecord (Rep a)) => Record -> Parser a     parseRecord r = to A.<$> gparseRecord r@@ -778,8 +777,6 @@     m >>= g = Parser $ \kf ks -> let ks' a = unParser (g a) kf ks                                  in unParser m kf ks'     {-# INLINE (>>=) #-}-    return a = Parser $ \_kf ks -> ks a-    {-# INLINE return #-}  #if MIN_VERSION_base(4,13,0) instance MonadFail Parser where@@ -794,10 +791,11 @@     {-# INLINE fmap #-}  instance Applicative Parser where-    pure  = return-    {-# INLINE pure #-}+     (<*>) = apP     {-# INLINE (<*>) #-}+    pure a = Parser $ \_kf ks -> ks a+    {-# INLINE pure #-}  instance Alternative Parser where     empty = fail "empty"@@ -842,9 +840,9 @@     gparseRecord :: Record -> Parser (f p)  instance GFromRecordSum f Record => GFromRecord (M1 i n f) where-    gparseRecord v = +    gparseRecord v =         case (IM.lookup n gparseRecordSum) of-            Nothing -> lengthMismatch n v +            Nothing -> lengthMismatch n v             Just p -> M1 <$> p v       where         n = V.length v@@ -853,15 +851,15 @@     gparseNamedRecord :: NamedRecord -> Parser (f p)  instance GFromRecordSum f NamedRecord => GFromNamedRecord (M1 i n f) where-    gparseNamedRecord v = +    gparseNamedRecord v =         foldr (\f p -> p <|> M1 <$> f v) empty (IM.elems gparseRecordSum)  class GFromRecordSum f r where     gparseRecordSum :: IM.IntMap (r -> Parser (f p))  instance (GFromRecordSum a r, GFromRecordSum b r) => GFromRecordSum (a :+: b) r where-    gparseRecordSum = -        IM.unionWith (\a b r -> a r <|> b r) +    gparseRecordSum =+        IM.unionWith (\a b r -> a r <|> b r)             (fmap (L1 <$>) <$> gparseRecordSum)             (fmap (R1 <$>) <$> gparseRecordSum) 
src/Data/CSV/Conduit/Parser/ByteString.hs view
@@ -58,15 +58,17 @@ row :: CSVSettings -> Parser (Maybe (Row ByteString)) row csvs = csvrow csvs <|> badrow +csvEndOfLine :: Parser ()+csvEndOfLine = (word8 10 >> return ()) <|> ((word8 13 *> word8 10) >> return ()) <|> (word8 13 >> return ())  badrow :: Parser (Maybe (Row ByteString)) badrow = P.takeWhile (not . C8.isEndOfLine) *>-         (C8.endOfLine <|> C8.endOfInput) *> return Nothing+         (csvEndOfLine <|> C8.endOfInput) *> return Nothing  csvrow :: CSVSettings -> Parser (Maybe (Row ByteString)) csvrow c =   let rowbody = (quotedField' <|> field c) `sepBy` C8.char (csvSep c)-      properrow = rowbody <* (C8.endOfLine <|> P.endOfInput)+      properrow = rowbody <* (csvEndOfLine <|> P.endOfInput)       quotedField' = case csvQuoteChar c of           Nothing -> mzero           Just q' -> try (quotedField q')@@ -93,5 +95,3 @@   f <- many (C8.notChar c <|> quoted)   _ <- C8.char c   return $ B8.pack f--
src/Data/CSV/Conduit/Parser/Text.hs view
@@ -58,15 +58,17 @@ row :: CSVSettings -> Parser (Maybe (Row Text)) row csvs = csvrow csvs <|> badrow +csvEndOfLine :: Parser ()+csvEndOfLine = (char '\n' >> return ()) <|> (string (T.pack "\r\n") >> return ()) <|> (char '\r' >> return ())  badrow :: Parser (Maybe (Row Text)) badrow = P.takeWhile (not . T.isEndOfLine) *>-         (T.endOfLine <|> T.endOfInput) *> return Nothing+         (csvEndOfLine <|> T.endOfInput) *> return Nothing  csvrow :: CSVSettings -> Parser (Maybe (Row Text)) csvrow c =   let rowbody = (quotedField' <|> field c) `sepBy` T.char (csvSep c)-      properrow = rowbody <* (T.endOfLine <|> P.endOfInput)+      properrow = rowbody <* (csvEndOfLine <|> P.endOfInput)       quotedField' = case csvQuoteChar c of           Nothing -> mzero           Just q' -> try (quotedField q')
src/Data/CSV/Conduit/Types.hs view
@@ -9,6 +9,7 @@ import qualified Data.Map.Ordered as MO ------------------------------------------------------------------------------- +data QuoteEmpty = DoQuoteEmpty | DontQuoteEmpty deriving (Show, Eq)  ------------------------------------------------------------------------------- -- | Settings for a CSV file. This library is intended to be flexible@@ -22,10 +23,12 @@     -- | Quote character that may sometimes be present around fields.     -- If 'Nothing' is given, the library will never expect quotation     -- even if it is present.-  , csvQuoteChar :: !(Maybe Char)-  } deriving (Read, Show, Eq)+  , csvQuoteCharAndStyle :: !(Maybe (Char, QuoteEmpty))+  } deriving (Show, Eq)  +csvQuoteChar :: CSVSettings -> Maybe Char+csvQuoteChar = (fst <$>) . csvQuoteCharAndStyle  ------------------------------------------------------------------------------- -- | Default settings for a CSV file.@@ -36,9 +39,14 @@ defCSVSettings :: CSVSettings defCSVSettings = CSVSettings   { csvSep = ','-  , csvQuoteChar = Just '"'+  , csvQuoteCharAndStyle = Just ('"', DoQuoteEmpty)   } +defDontQuoteEmptyCSVSettings :: CSVSettings+defDontQuoteEmptyCSVSettings = CSVSettings+  { csvSep = ','+  , csvQuoteCharAndStyle = Just ('"', DontQuoteEmpty)+  }  instance Default CSVSettings where     def = defCSVSettings
test/Test.hs view
@@ -9,10 +9,11 @@ import qualified Data.ByteString.Char8 as B import Data.CSV.Conduit import Data.CSV.Conduit.Conversion+import Data.CSV.Conduit.Types import qualified Data.Map as Map import qualified Data.Map.Ordered as OMap import Data.Monoid as M-import Data.Text+import Data.Text (Text) import qualified Data.Vector as V import System.Directory import Test.Framework (Test, defaultMain, testGroup)@@ -31,7 +32,10 @@ baseTests :: [Test] baseTests =   [ testCase "mapping with id works" test_identityMap,-    testCase "simple parsing works" test_simpleParse,+    testCase "simple parsing works" (test_simpleParse testFile1),+    testCase "simple parsing works for Mac-Excel" (test_simpleParse testFile3),+    testCase "simple parsing works for Windows-Excel" (test_simpleParse testFile4),+    testCase "fails parsing gracefully" test_parseFail,     testCase "OrderedMap" test_orderedMap   ] @@ -73,9 +77,9 @@     f :: Row Text -> [Row Text]     f = return -test_simpleParse :: IO ()-test_simpleParse = do-  (d :: V.Vector (MapRow B.ByteString)) <- readCSVFile csvSettings testFile1+test_simpleParse :: FilePath -> IO ()+test_simpleParse fp = do+  (d :: V.Vector (MapRow B.ByteString)) <- readCSVFile csvSettings fp   V.mapM_ assertRow d   where     assertRow r = v3 @=? (v1 + v2)@@ -84,6 +88,20 @@         v2 = readBS $ r Map.! "Col3"         v3 = readBS $ r Map.! "Sum" +test_parseFail :: IO ()+test_parseFail = do+  (d :: V.Vector (MapRow B.ByteString)) <- readCSVFile csvSettings testXLS+  errored <- catch (V.mapM_ assertRow d >> pure False) handler+  if errored then pure () else assertFailure "readCSVFile shouldn't read XLS"+  where+    handler :: ErrorCall -> IO Bool+    handler _ = pure True+    assertRow r = v3 @=? (v1 + v2)+      where+        v1 = readBS $ r Map.! "Col2"+        v2 = readBS $ r Map.! "Col3"+        v3 = readBS $ r Map.! "Sum"+ test_orderedMap :: IO () test_orderedMap = do   unorderedRes <-@@ -106,11 +124,16 @@     pairs = [("b", "bval"), ("a", "aval")]  csvSettings :: CSVSettings-csvSettings = defCSVSettings {csvQuoteChar = Just '`'}+csvSettings = defCSVSettings {csvQuoteCharAndStyle = Just ('`', DontQuoteEmpty)} -testFile1, testFile2 :: FilePath+testFile1, testFile2, testFile3, testFile4 :: FilePath testFile1 = "test/test.csv" testFile2 = "test/test.csv"+testFile3 = "test/test-mac-excel.csv"+testFile4 = "test/test-windows-excel.csv"++testXLS :: FilePath+testXLS = "test/test.xls"  readBS :: B.ByteString -> Int readBS = read . B.unpack
+ test/test-mac-excel.csv view
@@ -0,0 +1,1 @@+`Col1`,`Col2`,`Col3`,`Sum`
`A`,`2`,`3`,`5`
`B`,`3`,`4`,`7`
`Field using the quote char ``this is the in-quoted value```,`4`,`5`,`9`
+ test/test-windows-excel.csv view
@@ -0,0 +1,4 @@+Col1,Col2,Col3,Sum
+A,2,3,5
+B,3,4,7
+"Field using the quote char ""this is the in-quoted value""",4,5,9
+ test/test.xls view

binary file changed (absent → 6144 bytes)