{-# LANGUAGE Safe #-}
{- arch-tag: CSV and TSV utilities
Copyright (c) 2005-2011 John Goerzen <jgoerzen@complete.org>

All rights reserved.

For license and copyright information, see the file LICENSE
-}

{- |
   Module     : Data.CSV
   Copyright  : Copyright (C) 2005-2011 John Goerzen
   SPDX-License-Identifier: BSD-3-Clause

   Stability  : stable
   Portability: portable

Haskell Parsec parsers for comma-separated value (CSV) files.

Written by John Goerzen, jgoerzen\@complete.org
-}

module Data.CSV (csvFile, genCsvFile) where
import safe Text.ParserCombinators.Parsec
    ( char,
      noneOf,
      string,
      endBy,
      sepBy,
      (<?>),
      (<|>),
      many,
      try,
      GenParser,
      CharParser )
import Data.List (intersperse)

eol :: forall st. GenParser Char st String
eol :: forall st. GenParser Char st String
eol = (forall tok st a. GenParser tok st a -> GenParser tok st a
try forall a b. (a -> b) -> a -> b
$ forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
"\n\r") forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> (forall tok st a. GenParser tok st a -> GenParser tok st a
try forall a b. (a -> b) -> a -> b
$ forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
"\r\n") forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
"\n" forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|>
      forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
"\r" forall s u (m :: * -> *) a.
ParsecT s u m a -> String -> ParsecT s u m a
<?> String
"End of line"

cell :: GenParser Char st String
cell :: forall st. GenParser Char st String
cell = forall st. GenParser Char st String
quotedcell forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m [a]
many (forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m Char
noneOf String
",\n\r")

quotedchar :: GenParser Char st Char
quotedchar :: forall st. GenParser Char st Char
quotedchar = forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m Char
noneOf String
"\""
             forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> (forall tok st a. GenParser tok st a -> GenParser tok st a
try forall a b. (a -> b) -> a -> b
$ do String
_ <- forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
"\"\""
                           forall (m :: * -> *) a. Monad m => a -> m a
return Char
'"'
                 )
quotedcell :: CharParser st String
quotedcell :: forall st. GenParser Char st String
quotedcell = do Char
_ <- forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
'"'
                String
content <- forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m [a]
many forall st. GenParser Char st Char
quotedchar
                Char
_ <- forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
'"'
                forall (m :: * -> *) a. Monad m => a -> m a
return String
content

line :: GenParser Char st [String]
line :: forall st. GenParser Char st [String]
line = forall s (m :: * -> *) t u a sep.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m sep -> ParsecT s u m [a]
sepBy forall st. GenParser Char st String
cell (forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
',')

{- | Parse a Comma-Separated Value (CSV) file.  The return value is a list of
lines; each line is a list of cells; and each cell is a String.

Please note that CSV files may have a different number of cells on each line.
Also, it is impossible to distinguish a CSV line that has a cell with no data
from a CSV line that has no cells.

Here are some examples:

>Input (literal strings)          Parses As (Haskell String syntax)
>-------------------------------- ---------------------------------
>1,2,3                            [["1", "2", "3"]]
>
>l1                               [["l1"], ["l2"]]
>l2
>
> (empty line)                    [[""]]
>
>NQ,"Quoted"                      [["NQ", "Quoted"]]
>
>NQ,"Embedded""Quote"             [["NQ", "Embedded\"Quote"]]

To parse a String, you might use:

>import Text.ParserCombinators.Parsec
>import Data.String.CSV
>....
>parse csvFile "" mystring

To parse a file, you might instead use:

>do result <- parseFromFile csvFile "/path/to/file"

Please note that the result of parsing will be of type
(Either ParseError [[String]]).  A Left result indicates an error.
For more details, see the Parsec information.
-}

csvFile :: CharParser st [[String]]
csvFile :: forall st. CharParser st [[String]]
csvFile = forall s (m :: * -> *) t u a sep.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m sep -> ParsecT s u m [a]
endBy forall st. GenParser Char st [String]
line forall st. GenParser Char st String
eol

{- | Generate CSV data for a file.  The resulting string can be
written out to disk directly. -}
genCsvFile :: [[String]] -> String
genCsvFile :: [[String]] -> String
genCsvFile [[String]]
inp =
    [String] -> String
unlines forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map [String] -> String
csvline forall a b. (a -> b) -> a -> b
$ [[String]]
inp
    where csvline :: [String] -> String
          csvline :: [String] -> String
csvline [String]
l = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> [a] -> [a]
intersperse String
"," forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map String -> String
csvcells forall a b. (a -> b) -> a -> b
$ [String]
l
          csvcells :: String -> String
          csvcells :: String -> String
csvcells String
"" = String
""
          csvcells String
c = Char
'"' forall a. a -> [a] -> [a]
: String -> String
convcell String
c forall a. [a] -> [a] -> [a]
++ String
"\""
          convcell :: String -> String
          convcell :: String -> String
convcell String
c = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> String
convchar String
c
          convchar :: Char -> String
convchar Char
'"' = String
"\"\""
          convchar Char
x = [Char
x]