在Haskell中解析CSV/TSV文件 - Unicode字符

5
我正在尝试使用Haskell中的cassava/Data.Csv解析一个制表符分隔的文件。然而,如果我的CSV文件中有“奇怪”的(Unicode)字符,我就会遇到问题。然后我会得到一个解析错误(endOfInput)

根据命令行工具“file”的显示结果,我的文件采用“UTF-8 Unicode text”编码格式。我的Haskell代码如下:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}

import qualified Data.ByteString as C
import qualified System.IO.UTF8 as U
import qualified Data.ByteString.UTF8 as UB
import qualified Data.ByteString.Lazy.Char8 as DL
import qualified Codec.Binary.UTF8.String as US
import qualified Data.Text.Lazy.Encoding as EL
import qualified Data.ByteString.Lazy as L

import Data.Text.Encoding as E

-- Handle CSV / TSV files with ...
import Data.Csv
import qualified Data.Vector as V

import Data.Char -- ord

csvFile :: FilePath
csvFile = "myFile.txt"

-- Set delimiter to \t (tabulator)
myOptions = defaultDecodeOptions {
              decDelimiter = fromIntegral (ord '\t')
            }

main :: IO ()
main = do
  csvData <- L.readFile csvFile 
  case EL.decodeUtf8' csvData of 
   Left err -> print err
   Right dat ->
     case decodeWith myOptions NoHeader $ EL.encodeUtf8 dat of
       Left err -> putStrLn err
       Right v -> V.forM_ v $ \ (category :: String ,
                               user :: String ,
                               date :: String,
                               time :: String,
                               message :: String) -> do
         print message

我尝试使用decodingUtf8',使用来自Data.Char的谓词对输入进行预处理(过滤),还有其他一些尝试。但是仍然存在endOfFile错误。

我的CSV文件看起来像这样:

a   -   -   -   RT USE " Kenny"  Hahahahahahahahaha. #Emmen #Brandstapel
a   -   -   -   Uhm .. wat dan ook ????!!!! 

更直接地说:
a\t-\t-\t-\tRT USE " Kenny" • Hahahahahahahahaha. #Emmen #Brandstapel
a\t-\t-\t-\tUhm .. wat dan ook ????!!!! 

问题字符是 & 和 •(在我的完整文件中还有许多类似的字符)。我该怎么办,才能使cassava / Data.Csv正确读取我的文件?
编辑:我已经创建了以下预处理程序,用于在使用cassava进行解码之前转义我的文本(请参见tibbe的答案)。可能有更好的可能性,但到目前为止,这个方法运作良好!
import qualified Data.Text as T

preprocess :: T.Text -> T.Text
preprocess txt = cons '\"' $ T.snoc escaped '\"'
  where escaped = T.concatMap escaper txt

escaper :: Char -> T.Text
escaper c
  | c == '\t' = "\"\t\""
  | c == '\n' = "\"\n\""
  | c == '\"' = "\"\""
  | otherwise = T.singleton c

1
制表符分隔符在哪里(与字段内部的空格相对)?从上面粘贴的示例数据中很难看出来。您能否使用例如文字“<tab>”来发布数据,以显示制表符的位置?我假设最后一个字段以“RT USE…”开头。 - tibbe
抱歉,我没有意识到制表符被转换为空格了: a\t-\t-\t-\tRT USE " Kenny" • Hahahahahahahahaha. #Emmen #Brandstapel\na\t-\t-\t-\tUhm .. wat dan ook ????!!!! - Pold
1个回答

4
根据木薯文档:
非转义字段可以包含除双引号、逗号、回车和换行符以外的任何字符。
转义字段可以包含任何字符(但双引号需要转义)。
由于您的第一条记录中最后一个字段包含双引号,因此该字段需要用双引号转义,并且任何双引号都需要转义,如下所示:
a   -   -   -   "RT USE "" Kenny"" • Hahahahahahahahaha. #Emmen #Brandstapel"

这段代码对我有效:
import Data.ByteString.Lazy
import Data.Char
import Data.Csv
import Data.Text.Encoding
import Data.Vector

test :: Either String (Vector (String, String, String, String, String))
test = decodeWith
    defaultDecodeOptions {decDelimiter = fromIntegral $ ord '\t' }
    NoHeader
    (fromStrict $ encodeUtf8 "a\t-\t-\t-\t\"RT USE \"\" Kenny\"\" • Hahahahahahahahaha. #Emmen #Brandstapel\"")

请注意,我必须确保在类型为Text的文字量上使用encodeUtf8而不是直接使用ByteString文字量。对于ByteString,使用的IsString实例会截断每个Unicode代码点。


1
谢谢,这很好用,Unicode 字符不再引起错误。但是,为了转义带有双引号的字段,我需要处理这个字段。为了处理这个字段,我需要使用 cassava 解码我的 CSV。但是,使用 cassava 解码会导致解析错误,因为字段还没有被转义。我有几个 CSV 文件,每个文件中有数千条记录,因此手动转义它们不是一个选项。有没有办法在 cassava 中实现这一点(我想避免使用另一个工具预处理文件)? - Pold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接