module Text.Read.CSV (
readCSV, writeCSV, writeCSVstrict, csvTableP
) where
import Text.ParserCombinators.ReadP
import Control.Monad
import Data.List
import Data.Maybe
munchSpaces :: ReadP ()
munchSpaces =
do s <- look
skip s
where
skip (' ':s) = get >> skip s
skip _ = return ()
unescaped :: ReadP String
unescaped = munch (`notElem` ",\"\r\n\t")
notquote :: ReadP String
notquote = munch (/= '"')
singlequoted :: ReadP String
singlequoted = do
char '"'
s <- notquote
char '"'
return s
fullquoted :: ReadP String
fullquoted = fmap (intercalate "\"") (many1 singlequoted)
csvcell :: ReadP String
csvcell = munchSpaces >> (unescaped +++ fullquoted)
csvrow :: ReadP [String]
csvrow = sepBy1 csvcell (char ',')
crlf :: ReadP ()
crlf = optional (char '\r') >> char '\n' >> return ()
csvTableP :: ReadP [[String]]
csvTableP = sepBy1 csvrow crlf
csvfile :: ReadP [[String]]
csvfile = do
table <- csvTableP
optional crlf
eof
return table
readCSV :: String -> Maybe [[String]]
readCSV = fmap fst . listToMaybe . readP_to_S csvfile
escapecell :: String -> String
escapecell cs = "\"" ++ (cs >>= \case '"' -> "\"\""; x -> [x]) ++ "\""
escapeifneeded :: String -> String
escapeifneeded cs = if any (`elem` ",\"\r\n\t") cs then escapecell cs else cs
writeCSV :: [[String]] -> String
writeCSV = join . fmap ((++ "\n") . intercalate "," . fmap escapeifneeded)
writeCSVstrict :: [[String]] -> String
writeCSVstrict = intercalate "\r\n" . fmap (intercalate "," . fmap escapeifneeded)