在 Go 中使用 BOM 读取文件

问题描述 投票:0回答:5

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

unicode go byte-order-mark
5个回答
10
投票

没有标准的方法,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.File
的实例(
os.Open()
返回的)支持寻找并因此实现
io.Seeker
。请注意,对于 HTTP 响应的
Body
阅读器来说情况并非如此,因为您无法“倒带”它。
bufio.Buffer
通过执行一些缓冲(显然)来解决不可搜索流的这个特性——这就是允许你在上面
UnreadRune()
的原因。

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


5
投票

您可以使用 utfbom 包。它包装

io.Reader
,根据需要检测和丢弃BOM。它还可以返回 BOM 检测到的编码。


4
投票

我想我会在这里添加从 string strip Byte Order Mark 序列的方法——而不是直接乱用字节(如上所示)。

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)'

干杯!


3
投票

在 Go 核心包中没有执行此操作的标准方法。遵循 Unicode 标准。

Unicode 字节顺序标记 (BOM) 常见问题解答


0
投票

我们使用 transform 包以简单的方式读取 CSV 文件(可能以 UTF8、UTF8-with-BOM、UTF16 从 Excel 保存):

import (
    "encoding/csv"
    "golang.org/x/text/encoding"
    "golang.org/x/text/encoding/unicode"
    "golang.org/x/text/transform"
    "io"
}

// BOMAwareCSVReader will detect a UTF BOM (Byte Order Mark) at the
// start of the data and transform to UTF8 accordingly.
// If there is no BOM, it will read the data without any transformation.
func BOMAwareCSVReader(reader io.Reader) *csv.Reader {
    var transformer = unicode.BOMOverride(encoding.Nop.NewDecoder())
    return csv.NewReader(transform.NewReader(reader, transformer))
}

我们正在使用 Go 1.18.

© www.soinside.com 2019 - 2024. All rights reserved.