在Go中读取带有BOM的文件

时间:2014-01-27 01:37:06

标签: unicode go byte-order-mark

我需要读取可能包含或不包含字节顺序标记的Unicode文件。我当然可以自己检查文件的前几个字节,如果找到则丢弃BOM。但在此之前,是否有任何标准的方法可以在核心库或第三方中进行此操作?

4 个答案:

答案 0 :(得分:4)

没有标准的方式,IIRC(标准库实际上是一个错误的层来实现这样的检查)所以这里有两个你自己如何处理它的例子。

一种是在数据流上方使用缓冲读取器:

import (
    "bufio"
    "os"
    "log"
)

func main() {
    fd, err := os.Open("filename")
    if err != nil {
        log.Fatal(err)
    }
    defer closeOrDie(fd)
    br := bufio.NewReader(fd)
    r, _, err := br.ReadRune()
    if err != nil {
        log.Fatal(err)
    }
    if r != '\uFEFF' {
        br.UnreadRune() // Not a BOM -- put the rune back
    }
    // Now work with br as you would do with fd
    // ...
}

另一种与实现io.Seeker接口的对象一起工作的方法是读取前三个字节,如果它们不是BOM,则io.Seek()返回到开头,如:

import (
    "os"
    "log"
)

func main() {
    fd, err := os.Open("filename")
    if err != nil {
        log.Fatal(err)
    }
    defer closeOrDie(fd)
    bom := [3]byte
    _, err = io.ReadFull(fd, bom[:])
    if err != nil {
        log.Fatal(err)
    }
    if bom[0] != 0xef || bom[1] != 0xbb || bom[2] != 0xbf {
        _, err = fd.Seek(0, 0) // Not a BOM -- seek back to the beginning
        if err != nil {
            log.Fatal(err)
        }
    }
    // The next read operation on fd will read real data
    // ...
}

这是可能的,因为*os.Fileos.Open()返回)的实例支持寻求并因此实现io.Seeker。请注意,对于HTTP响应的Body读者而言,情况并非如此,因为您无法“回放”它。 bufio.Buffer通过执行一些缓冲(显然)来解决不可搜索流的这一特性 - 这就是允许你在其上UnreadRune()的内容。

请注意,这两个示例都假设我们正在处理的文件是以UTF-8编码的。如果你需要处理其他(或未知)编码,事情会变得更复杂。

答案 1 :(得分:3)

在Go核心包中没有标准的方法。遵循Unicode标准。

Unicode Byte Order Mark (BOM) FAQ

答案 2 :(得分:1)

您可以使用utfbom包。它包装io.Reader,根据需要检测并丢弃BOM。它还可以返回BOM检测到的编码。

答案 3 :(得分:1)

我想我会在这里添加从字符串剥离字节顺序标记序列的方法——而不是乱搞字节直接(如上图)。

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "\uFEFF is a string that starts with a Byte Order Mark"
    fmt.Printf("before: '%v' (len=%v)\n", s, len(s))

    ByteOrderMarkAsString := string('\uFEFF')

    if strings.HasPrefix(s, ByteOrderMarkAsString) {

        fmt.Printf("Found leading Byte Order Mark sequence!\n")
        
        s = strings.TrimPrefix(s, ByteOrderMarkAsString)
    }
    fmt.Printf("after: '%v' (len=%v)\n", s, len(s)) 
}

其他“字符串”函数也应该可以工作。

这是打印出来的:

before: ' is a string that starts with a Byte Order Mark (len=50)'
Found leading Byte Order Mark sequence!
after: ' is a string that starts with a Byte Order Mark (len=47)'

干杯!