Decorator Pattern で BOM を除去する
最近 CSV ファイルを扱う必要がありまして。 Windows では有名な A5:SQL Mk-2 のエクスポート機能を使って吸い上げたデータを再利用するのですが,例によって BOM (Byte Order Mark) が付いてるのですよ。
BOM は忘れた頃にやってくる(遠い目)
で,最近読んだ『実用 Go言語』に Decorator Pattern で BOM を除去する方法が載っていた(8章)ので,早速試してみることにした。こんな感じ。
package main
import (
"fmt"
"io"
"strings"
"github.com/spkg/bom"
)
const text = "\xEF\xBB\xBFhello"
func main() {
fmt.Println([]byte(text))
r := bom.NewReader(strings.NewReader(text))
b, err := io.ReadAll(r)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(b)
}
これを実行すると
$ go run sample1.go
[239 187 191 104 101 108 108 111]
[104 101 108 108 111]
と出力される。先頭の BOM が除去されているのがお分かりだろうか。
これとは別に github.com/dimchansky/utfbom パッケージってのがあって,同じように
package main
import (
"fmt"
"io"
"strings"
"github.com/dimchansky/utfbom"
)
const text = "\xEF\xBB\xBFhello"
func main() {
fmt.Println([]byte(text))
r := utfbom.SkipOnly(strings.NewReader(text))
b, err := io.ReadAll(r)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(b)
}
と書けば全く同じ出力を得られた。
github.com/spkg/bom パッケージは,ソースコードを見ると分かるが,とてもシンプルな作りになっていて, UTF-8 エンコーディングに限るならお手軽に使えるのがよい。もう一方の github.com/dimchansky/utfbom パッケージは UTF-8 以外に UTF-16 や UTF-32 にも対応していて,先程のコードを
r, enc := utfbom.Skip(strings.NewReader(text))
と置き換えれば UTF テキストのエンコーディングも取得できる。必要に応じて使い分けるのがいいだろう。
ただし,いずれのパッケージも先頭の BOM しか取り除いてくれない。何らかの理由(BOM 付きテキストを安直に結合した場合とか)でテキストの先頭以外に紛れ込んでいる BOM があっても素通ししてしまう。まぁ,今時そういうケースは殆どないだろうが。
CSV ファイルは巨大になりがちで,システムの規模によってはすぐに十万レコードとか百万レコードとかになってしまう。この点で csv.Reader 型はとてもよく出来ていて, Read() メソッドを使って順次アクセスで1レコードづつ切り出して返してくれる。
func (r *Reader) Read() (record []string, err error)
これを活かすのであれば Decorator Pattern で入力をラッピングするのが最善だろう。たとえばこんな感じ。
package main
import (
"encoding/csv"
"errors"
"fmt"
"io"
"os"
"github.com/spkg/bom"
)
func main() {
file, err := os.Open("./sample3.csv")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
defer file.Close()
r := csv.NewReader(bom.NewReader(file))
for {
row, err := r.Read()
if err != nil {
if !errors.Is(err, io.EOF) {
fmt.Fprintln(os.Stderr, err)
return
}
break
}
fmt.Println(row)
}
}
そもそも csv.Reader 型自体が入力のラッパーである点に注目。 io.Reader interface 型をベースにした Decorator Pattern で,動的な機能追加が簡単に出来てしまうのが嬉しい。
なお,文字エンコーディング変換も Decorator Pattern で実装できる。この記事では詳細は割愛するが,ググればあちこちに見つかると思うので探してみていただきたい。
Discussion