Я предполагаю, что вы начинаете с чем-то вроде
import Prelude hiding (getContents, putStrLn)
import Data.ByteString
import Text.Parsec.ByteString
Вот что я получил до сих пор. Есть две версии. Оба компилируются. Наверное, и не то, что вы хотите, но они должны помочь в обсуждении и помочь вам прояснить ваш вопрос.
Что-то я заметил по пути:
- Если вы
import Text.Parsec.ByteString
то это использует uncons
из Data.ByteString.Char8, который, в свою очередь, использует w2c
из Data.ByteString.Internal, чтобы преобразовать все прочитать байты Char
s. Это позволяет сообщать об ошибках номера строки и столбца Parsec для разумного использования, а также позволяет без проблем использовать string
и друзей.
Таким образом, простой вариант синтаксического анализа CSV, который делает именно то, что:
import Prelude hiding (getContents, putStrLn)
import Data.ByteString (ByteString)
import qualified Prelude (getContents, putStrLn)
import qualified Data.ByteString as ByteString (getContents)
import Text.Parsec
import Text.Parsec.ByteString
csvFile :: Parser [[String]]
csvFile = endBy line eol
line :: Parser [String]
line = sepBy cell (char ',')
cell :: Parser String
cell = quotedCell <|> many (noneOf ",\n\r")
quotedCell :: Parser String
quotedCell =
do _ <- char '"'
content <- many quotedChar
_ <- char '"' <?> "quote at end of cell"
return content
quotedChar :: Parser Char
quotedChar =
noneOf "\""
<|> try (string "\"\"" >> return '"')
eol :: Parser String
eol = try (string "\n\r")
<|> try (string "\r\n")
<|> string "\n"
<|> string "\r"
<?> "end of line"
parseCSV :: ByteString -> Either ParseError [[String]]
parseCSV = parse csvFile "(unknown)"
main :: IO()
main =
do c <- ByteString.getContents
case parse csvFile "(stdin)" c of
Left e -> do Prelude.putStrLn "Error parsing input:"
print e
Right r -> mapM_ print r
Но это было так тривиально, чтобы получить работу, что я предполагаю, что это не может быть то, что вы хотите. Возможно, вы хотите, чтобы все оставалось ByteString
или [Word8]
или что-то подобное на всем протяжении? Отсюда моя вторая попытка ниже. Я все еще import
ing Text.Parsec.ByteString
, что может быть ошибкой, и код безнадежно пронизан конверсиями.
Но, он компилирует и имеет полные аннотации типов, и, следовательно, должны сделать звук отправной точкой.
import Prelude hiding (getContents, putStrLn)
import Data.ByteString (ByteString)
import Control.Monad (liftM)
import qualified Prelude (getContents, putStrLn)
import qualified Data.ByteString as ByteString (pack, getContents)
import qualified Data.ByteString.Char8 as Char8 (pack)
import Data.Word (Word8)
import Data.ByteString.Internal (c2w)
import Text.Parsec ((<|>), (<?>), parse, try, endBy, sepBy, many)
import Text.Parsec.ByteString
import Text.Parsec.Prim (tokens, tokenPrim)
import Text.Parsec.Pos (updatePosChar, updatePosString)
import Text.Parsec.Error (ParseError)
csvFile :: Parser [[ByteString]]
csvFile = endBy line eol
line :: Parser [ByteString]
line = sepBy cell (char ',')
cell :: Parser ByteString
cell = quotedCell <|> liftM ByteString.pack (many (noneOf ",\n\r"))
quotedCell :: Parser ByteString
quotedCell =
do _ <- char '"'
content <- many quotedChar
_ <- char '"' <?> "quote at end of cell"
return (ByteString.pack content)
quotedChar :: Parser Word8
quotedChar =
noneOf "\""
<|> try (string "\"\"" >> return (c2w '"'))
eol :: Parser ByteString
eol = try (string "\n\r")
<|> try (string "\r\n")
<|> string "\n"
<|> string "\r"
<?> "end of line"
parseCSV :: ByteString -> Either ParseError [[ByteString]]
parseCSV = parse csvFile "(unknown)"
main :: IO()
main =
do c <- ByteString.getContents
case parse csvFile "(stdin)" c of
Left e -> do Prelude.putStrLn "Error parsing input:"
print e
Right r -> mapM_ print r
-- replacements for some of the functions in the Parsec library
noneOf :: String -> Parser Word8
noneOf cs = satisfy (\b -> b `notElem` [c2w c | c <- cs])
char :: Char -> Parser Word8
char c = byte (c2w c)
byte :: Word8 -> Parser Word8
byte c = satisfy (==c) <?> show [c]
satisfy :: (Word8 -> Bool) -> Parser Word8
satisfy f = tokenPrim (\c -> show [c])
(\pos c _cs -> updatePosChar pos c)
(\c -> if f (c2w c) then Just (c2w c) else Nothing)
string :: String -> Parser ByteString
string s = liftM Char8.pack (tokens show updatePosString s)
Возможно, ваша забота, эффективность-накрест, должны быть эти две ByteString.pack
инструкции, в определениях cell
и quotedCell
. Вы можете попытаться заменить модуль Text.Parsec.ByteString так, чтобы вместо того, чтобы «сделать строгий ByteStrings экземпляр Stream
с типом токена Char
», вы делаете ByteStrings экземпляром Stream
с типом токена Word8
, но это не поможет вам с эффективность, это просто даст вам головную боль, пытающуюся перекомпоновать все функции sourcePos, чтобы отслеживать вашу позицию на входе для сообщений об ошибках.
Нет, способ сделать его более эффективным было бы изменить типы char
, quotedChar
и string
к Parser [Word8]
и типы line
и csvFile
к Parser [[Word8]]
и Parser [[[Word8]]]
соответственно. Вы можете даже изменить тип eol
на Parser()
. Необходимые изменения будут выглядеть примерно так:
cell :: Parser [Word8]
cell = quotedCell <|> many (noneOf ",\n\r")
quotedCell :: Parser [Word8]
quotedCell =
do _ <- char '"'
content <- many quotedChar
_ <- char '"' <?> "quote at end of cell"
return content
string :: String -> Parser [Word8]
string s = [c2w c | c <- (tokens show updatePosString s)]
Вам не нужно беспокоиться обо всех вызовов c2w
с точки зрения эффективности обеспокоен, потому что они ничего не стоят.
Если это не ответит на ваш вопрос, скажите, что бы это было.
'attoparsec' намного быстрее и имеет API, очень похожий на Parsec, поэтому я действительно рекомендую вам изучить его! –
Я настоятельно рекомендую вам использовать библиотеку 'cassava' для анализа CSV. Это очень быстро (он использует 'attoparsec' внутри), и он очень прост в использовании. –
Я предполагаю, что вы имеете в виду на обратной стороне? После этого вам придется делать свой собственный «пакет», т. Е. 'pack $ string" foobar "' (помня, чтобы думать о кодировании.) Кроме этого, '' строка Parsecs' отлично работает на 'ByteString' на стороне ввода. – Sarah