@@ -0,0 +1,108 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE ViewPatterns #-}
6+{- |
7+ Module : Text.Pandoc.Readers.RST
8+ Copyright : Copyright (C) 2006-2019 John MacFarlane
9+ License : GNU GPL, version 2 or above
10+
11+ Maintainer : John MacFarlane <jgm@berkeley.edu>
12+ Stability : alpha
13+ Portability : portable
14+
15+Conversion from CSV to a 'Pandoc' table.
16+-}
17+module Text.Pandoc.Readers.CSV ( readCSV ) where
18+import Prelude
19+import Data.Text (Text)
20+import qualified Data.Text as T
21+import Text.Pandoc.Definition
22+import qualified Text.Pandoc.Builder as B
23+import Text.Parsec
24+import Text.Parsec.Text (Parser)
25+import Text.Pandoc.Class (PandocMonad)
26+import Text.Pandoc.Shared (crFilter)
27+import Text.Pandoc.Error
28+import Text.Pandoc.Options (ReaderOptions(..))
29+import Control.Monad.Except (throwError)
30+
31+readCSV :: PandocMonad m
32+ => ReaderOptions -- ^ Reader options
33+ -> Text -- ^ Text to parse (assuming @'\n'@ line endings)
34+ -> m Pandoc
35+readCSV opts s = do
36+ let columns = readerColumns opts
37+ case parse pCSV "input" (crFilter s) of
38+ Right (r:rs) -> return $ B.doc $ B.table capt (zip aligns widths) hdrs rows
39+ where capt = mempty
40+ numcols = length r
41+ hdrs = map (B.plain . B.text) r
42+ rows = map (map (B.plain . B.text)) rs
43+ maximum' [] = 0
44+ maximum' xs = maximum xs
45+ aligns = replicate numcols AlignDefault
46+ widths = replicate numcols 0
47+ Right [] -> return $ B.doc mempty
48+ Left e -> throwError $ PandocParsecError s e
49+
50+{- from RFC 4180
51+
52+ The ABNF grammar [2] appears as follows:
53+
54+ file = [header CRLF] record *(CRLF record) [CRLF]
55+
56+ header = name *(COMMA name)
57+
58+ record = field *(COMMA field)
59+
60+ name = field
61+
62+ field = (escaped / non-escaped)
63+
64+ escaped = DQUOTE *(TEXTDATA / COMMA / CR / LF / 2DQUOTE) DQUOTE
65+
66+ non-escaped = *TEXTDATA
67+
68+ COMMA = %x2C
69+
70+ CR = %x0D ;as per section 6.1 of RFC 2234 [2]
71+
72+ DQUOTE = %x22 ;as per section 6.1 of RFC 2234 [2]
73+
74+ LF = %x0A ;as per section 6.1 of RFC 2234 [2]
75+
76+ CRLF = CR LF ;as per section 6.1 of RFC 2234 [2]
77+
78+ TEXTDATA = %x20-21 / %x23-2B / %x2D-7E
79+-}
80+
81+pCSV :: Parser [[Text]]
82+pCSV = many pRecord
83+
84+pRecord :: Parser [Text]
85+pRecord = do
86+ x <- pField
87+ xs <- many $ pComma >> pField
88+ () <$ newline <|> eof
89+ return (x:xs)
90+
91+pField :: Parser Text
92+pField = pEscaped <|> pUnescaped
93+
94+pComma :: Parser Char
95+pComma = char ','
96+
97+pUnescaped :: Parser Text
98+pUnescaped = T.strip . T.pack <$> many1 (noneOf "\n\r\",")
99+
100+pEscaped :: Parser Text
101+pEscaped = do
102+ char '"'
103+ t <- T.pack <$> many (pDoubledQuote <|> noneOf "\"")
104+ char '"'
105+ return t
106+
107+pDoubledQuote :: Parser Char
108+pDoubledQuote = try $ char '"' >> char '"'