问题 haskell - 无效的代码页字节序列


readFile "file.html"
"start of the file... *** Exception: file.html: hGetContents: invalid argument (invalid code page byte sequence)

这是用记事本++创建的UTF-8文件...如何在haskell中读取文件?


8735
2017-10-15 20:12


起源

您是否可以发布一个显示此错误的最小文件的十六进制转储?我无法复制你的错误。 - ghoti
使用此字符: č - Karoly Horvath
你的语言环境是什么?它是一个utf-8或ucs2ish(记事本++建议使用Windows)? - Daniel Fischer
它的窗口......不知道我的语言环境(意思是:如果可能的话,我不想改变它) - Karoly Horvath
文件开头是否有BOM标记(0xFE 0xFF)?你可以发布文件开头的hexdump(比如0x100字节)吗? - Dmytro Sirenko


答案:


默认情况下,文件是在系统区域设置中读取的,因此如果您有使用非标准编码的文件,则需要自己设置文件句柄的编码。

foo = do
    handle <- openFile "file.html" ReadMode
    hSetEncoding handle utf8_bom
    contents <- hGetContents handle
    doSomethingWithContents
    hClose handle

应该让你开始。请注意,这不包含错误处理,因此更好的方法

import Control.Exception -- for bracket

foo = bracket
        (openFile "file.html" ReadMode >>= \h -> hSetEncoding h utf8_bom >> return h)
        hClose
        (\h -> hGetContents h >>= doSomething)

要么

foo = withFile "file.html" ReadMode $
        \h -> do hSetEncoding h utf8_bom
                 contents <- hGetContents h
                 doSomethingWith contents

13
2017-10-15 20:33



我提出了类似的东西(比如你的 foo 虽然我不知道,但是 _bom..)。是否有可能在外面进行计算,就像我一样 readFile?该 foo 示例有效,如果我不使用 hClose 线(这对我的玩具程序来说没问题,但想知道“正确的方法”..我是否必须返回类似的东西 IO (String, Handle)? ) - Karoly Horvath
如果您需要懒惰地阅读文件,这有点困难。该 hClose 在计算返回时关闭文件,这可能在读取任何文件之前发生。因此,如果您可以一次将整个文件放在内存中,那么操作简单 length contents `seq` return contents 会强制读取整个文件,你可以在外面进行处理。否则,最好将文件读作懒惰 ByteString 并转换(使用 Data.ByteString.Lazy.UTF8.toString 来自 utf8-string 包)。 - Daniel Fischer
我不知道怎么样 ByteString 帮助/使事情变得更容易(虽然我是初学者).. readFile 很懒,对吧?那怎么关闭文件呢?它会检查最后一个字符是否已被读取?我能以某种方式模仿它的行为吗? - Karoly Horvath
readFile 它有点神奇,它将文件句柄置于半封闭状态,并且只有在文件结束后才真正关闭。 Data.ByteString.Lazy的 readFile 也这样做。你可以模仿这种行为,但你需要使用低级文件句柄操作,我会把它关闭一段时间。用懒惰 ByteStrings允许你懒惰地阅读文件(他们不关心编码,与之相反) String API),并逐步使用它。如果文件足够小,则在返回其内容之前强制它被完全读取更容易。 - Daniel Fischer


根据 这个网站,你的6个字节解码如下:

EF BB BF -> ZERO WIDTH NO-BREAK SPACE (i.e. the BOM, although its not needed in UTF-8
C4 8D    -> LATIN SMALL LETTER C WITH CARON (what you said)
0D       -> CARRIAGE RETURN (CR)

所以它是一个合法的UTF-8序列。

但是标准Prelude函数最初只是执行ASCII。我不知道他们现在做了什么,但看到这个问题 GHC / Haskell如何决定从哪个字符编码解码/编码? 一些更多的想法。然后使用 http://hackage.haskell.org/package/utf8-string 而不是Prelude功能。


1
2017-10-15 21:20